#!/usr/bin/python
# -*- coding: latin1 -*-

# latlon.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       May 10, 2007            bar
#       July 5, 2007            bar     name typo
#       July 19, 2007           bar     feet distance
#       July 25, 2007           bar     metersPerNauticalMile
#       October 7, 2007         bar     parse lat lon (parially - needs lots of work with "s for seconds" and non-3-number stuff and "xE Ny" sorts of things)
#       October 12, 2007        bar     move lat lon routines from tz_gps to here
#       October 22, 2007        bar     more parsing stuff
#       November 15, 2007       bar     explicit lat= lon= parsing
#                                       and a lot else including returning the sign multiplier from the deg/min/sec split routines (as -0.00001 won't return a negative zero degrees)
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       November 30, 2007       bar     note
#       December 5, 2007        bar     clean out spurious periods at the top of parsing
#                                       handle spurious commas better
#                                       tighter handling of colons and "degrees"
#                                       handle geo: url
#       December 15, 2007       bar     nsew_lat_lon_string
#       December 20, 2007       bar     limit functions
#       December 21, 2007       bar     distance_in_lat_lon_here
#       December 23, 2007       bar     slightly more exact milesPerNauticalMile
#                                       convert_lat_lon_to_x_y_z() and convert_x_y_z_to_lat_lon()
#       December 25, 2007       bar     allow long and lng
#       January 5, 2008         bar     fix calcDistance() for date-line crossings
#       March 9, 2008           bar     deg min.dec output routines
#       April 27, 2008          bar     'cause of Panaramio, add 0xa7(dos) and 0xba(unicode) to the list of degree characters
#       May 14, 2008            bar     signed_lat_lon_string()
#       May 16, 2008            bar     parse nmea lat lon values
#       May 17, 2008            bar     email adr
#       June 26, 2008           bar     an_xy_point and an_xyz_point
#       June 29, 2008           bar     better distance_from_line routine in 3d, along with ability to return the point that's closest
#       August 28, 2008         bar     round when converting to 10 thousanths of a minute
#       October 27, 2008        bar     feetPerMeter
#       April 11, 2009          bar     no 'as' named variable
#       November 18, 2009       bar     more stuff
#       January 1, 2011         bar     deal with unicode by making this file latin1 encoded
#       June 1, 2011            bar     try reversing NS/EW in case they are backward and we filtered it 'cause the NS was over 90
#                                       and do negative look-behind things instead of \b for start of nsew (note: haven't checked for all instances of them)
#       November 29, 2011       bar     pyflake cleanup
#       --eodstamps--
##      \file
#
#
#
#       Latitude Longitude stuff
#
#       Test output is in latlon.cmp (plus == None)
#
#       TODO:
#
#           parsing:
#
#               convert NSEW to north south east west (now that the #d #m #s fixing logic is really in)
#               then look explicitly for north south east west rather than fighting the single letter problem (which, well, now seems pretty well solved)
#
#               Common errors not handled yet:
#                   N 160, W 32             Reversing ew/ns
#                   45' 23", 101' 34"       ' and " for degrees, minutes
#
#       BUGS:
#
#           "23 deg 17 min",        is goofed as lat/lon 23/17.
#
#

import  math
import  re
import  sys


def lat_in_world(lat) :
    """ Return a value from -90 to +90. """

    return(min(90, max(-90, lat)))


def lat_in_world_modulo(lat) :
    """ Return a value from -90 to +90 assuming that arithmetic put a value over the pole. """

    if  -90 <= lat <= 90 :
        return(lat)

    m   = False
    if  lat < 0 :
        m   = True
        lat = -lat

    v       = int(lat)
    frac    = lat - v
    lat     = v % 360

    if   lat > 270 :
         lat = lat - 360
    elif lat > 90 :
         frac   = -frac
         lat = 180 - lat

    lat    += frac
    if  (not lat) and ((v % 180) == 90) :
        lat = 90.0

    if  lat >  90.0 :
        lat =  180.0 - lat
    elif lat < -90.0 :
        lat = -180.0 - lat

    if  m :
        lat = -lat

    return(lat)


def lon_in_world(lat) :
    """ Return a value from -180 to +180. """

    return(min(180, max(-180, lat)))


def lon_in_world_modulo(lon) :
    """ Return a value from -180 to +180 assuming that arithmetic put a value around the world. """

    if  -180 <= lon <= 180 :
        return(lon)

    m   = False
    if  lon < 0 :
        m   = True
        lon = -lon

    v       = int(lon)
    frac    = lon - v
    lon     = v % 360

    if   lon > 180 :
         lon = lon - 360

    lon    += frac
    if  (not lon) and ((v % 360) == 180) :
        lon = 180.0

    if  lon > 180.0 :
        lon = lon - 360.0

    if  m :
        lon = -lon

    return(lon)





class an_xy_point :
    def __init__(me, x, y) :
        me.x    = float(x)
        me.y    = float(y)

    def distance_from(me, p) :
        return(math.sqrt(((me.x - p.x) * (me.x - p.x)) + ((me.y - p.y) * (me.y - p.y))))

    def distance_from_line(me, p1, p2) :
        n       = abs(((p2.x - p1.x) * (p1.y - me.y)) - ((p1.x - me.x) * (p2.y - p1.y)))
        d       = p1.distance_from(p2)

        try :
            d       = n / p1.distance_from(p2)
        except ValueError :
            return(me.distance_from(p1))
        except ZeroDivisionError :
            return(me.distance_from(p1))
        return(d)


    def __str__(me) :
        return("%f,%f" % ( me.x, me.y ) )


    pass        # an_xy_point


class an_xyz_point :
    def __init__(me, x, y, z) :
        me.x    = float(x)
        me.y    = float(y)
        me.z    = float(z)

    def distance_from(me, p) :
        return(math.sqrt(((me.x - p.x) * (me.x - p.x)) + ((me.y - p.y) * (me.y - p.y)) + ((me.z - p.z) * (me.z - p.z))))

    def dist_from_line(me, p1, p2) :
        """ http://solvedproblems.wordpress.com/2008/03/11/distance-of-a-point-from-a-line-in-3d/ """

        ab      = p1.distance_from(p2)
        if  ab == 0.0 :
            return(me.distance_from(p1))

        ap      = p1.distance_from(me)
        bp      = p2.distance_from(me)
        s       = (ab + ap + bp) / 2.0
        try :
            d       = 2.0 * math.sqrt(s * abs(s - ab) * abs(s - ap) * abs(s - bp)) / ab
        except ValueError :
            return(me.distance_from(p1))
        except ZeroDivisionError :
            return(me.distance_from(p1))

        return(d)


    def dot(me, p) :
        return((me.x * p.x) + (me.y * p.y) + (me.z * p.z))

    def closest_point_on_line(me, p1, p2) :
        """ http://geometryalgorithms.com/Archive/algorithm_0102/algorithm_0102.htm """

        v       = an_xyz_point(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
        w       = an_xyz_point(me.x - p1.x, me.y - p1.y, me.z - p1.z)

        c1      = w.dot(v)
        c2      = v.dot(v)
        try :
            b   = c1 / c2
        except ZeroDivisionError :
            return(me.distance_from(p1))

        return(an_xyz_point(p1.x + b * v.x, p1.y + b * v.y, p1.z + b * v.z))

    def distance_from_line(me, p1, p2) :
        return(me.distance_from(me.closest_point_on_line(p1, p2)))


    def __str__(me) :
        return("%f,%f,%f" % ( me.x, me.y, me.z ) )


    pass        # an_xyz_point





#       http://www.geomidpoint.com/calculation.html is the best description I found

def convert_lat_lon_to_x_y_z(lat, lon) :
    """ Convert a latitude and longitude in to X Y and Z cartesian values - where 1.0 is the surface of the earth (this might change). """

    lat    *= rad
    lon    *= rad

    z       = math.sin(lat)
    cz      = math.cos(lat)

    y       = math.sin(lon) * cz
    x       = math.cos(lon) * cz

    return( ( x, y, z ) )


def convert_lat_lon_to_xyz_point(lat, lon) :
    """ Convert a latitude and longitude in to X Y and Z cartesian values - where 1.0 is the surface of the earth (this might change). """

    ( x, y, z)  = convert_lat_lon_to_x_y_z(lat, lon)
    return(an_xyz_point(x, y, z))



def convert_x_y_z_to_lat_lon(x, y, z) :
    """ Convert an X Y and Z point back to latitude and longitude. """

    lon     = math.atan2(y, x)

    d       = math.sqrt(x * x + y * y)
    lat     = math.atan2(z, d)
    #   lat = math.asin(z)                      # note: though inside -1.0 <= z <= 1.0 this doesn't raise and exception, and it gets the right value if we're just inverting original values, consider the effects of if xyz are averaged

    return( ( lat / rad, lon / rad ) )

def convert_xyz_point_to_lat_lon_xy_point(p) :
    """ Convert an X Y and Z point back to latitude and longitude. """

    ( x, y )    = convert_x_y_z_to_lat_lon(p.x, p.y, p.z)

    return(an_xy_point(x, y))



#
#   Quicky from http://www.zachary.com/s/blog/2005/01/12/python_zipcode_geo-programming
#
#   It assumes an incorrect sherical model of the earth. (Note: The net code doesn't handle -180 crossovers. It simply did lon2-lon1.)
#

nauticalMilePerLat          = 60.00721
nauticalMilePerLongitude    = 60.10793
rad                         = math.pi / 180.0

def calcDistance(lat1, lon1, lat2, lon2):
    """
        Caclulate distance between two lat lons in nautical miles
    """

    yDistance   = (lat2 - lat1) * nauticalMilePerLat

    lon1        = lon_in_world_modulo(lon1)
    lon2        = lon_in_world_modulo(lon2)
    if  lon2 < lon1 :
        ( lon1, lon2 )  = ( lon2, lon1 )
    ld          = min(lon2 - lon1, lon1 + 360 - lon2)

    xDistance   = (math.cos(lat1 * rad) + math.cos(lat2 * rad)) * ld * (nauticalMilePerLongitude / 2.0)

    distance    = math.sqrt( yDistance**2 + xDistance**2 )

    return(distance)



def distance_in_lat_lon_here(at_lat, at_lon, distance) :
    """
        Return how many degrees one would go in latitude if one moved the given distance.
        And how many degrees in longitude.
    """

    lat         = (((at_lat * nauticalMilePerLat) + distance) / nauticalMilePerLat) - at_lat

    lon         = (distance / calcDistance(at_lat, at_lon, at_lat, at_lon + 1.0))   - at_lon

    return( ( lat, lon ) )



milesPerNauticalMile        = 1.150779
metersPerNauticalMile       = 1852.0
feetPerMeter                = 3.2808399



nauticalEarthRadius         = 3438.145          # (equator 3,443.918 nmi + polar 3,432.372 nmi) / 2

if  False :
    mp1                     = an_xy_point(0,         0)
    mp2                     = an_xy_point(0.0000001, 0)
    mpd                     = calcDistance(mp1.x, mp1.y, mp2.x, mp2.y)
    xyzd                    = convert_lat_lon_to_xyz_point(mp1.x, mp1.y).distance_from(convert_lat_lon_to_xyz_point(mp2.x, mp2.y))
    nauticalEarthRadius     = mpd / xyzd        # comes out about the same
    print "radius", nauticalEarthRadius

    sys.exit(1)


def lat_lon_in_degrees_minutes_10_thousandth_minutes(ll) :
    """
        Note that the proper degrees is abs([0]) * [2], which can be negative zero
    """

    neg     =  1
    if  ll < 0.0 :
        ll  = -ll
        neg = -1

    f       = float(ll) - float(int(ll))

    m       = f * 60.0
    h       = int((m - int(m)) * 10000.0 + 0.5)

    if  neg < 0 :
        ll  = -ll

    return( [ int(ll), int(m), h, neg ] )





def lat_lon_in_degrees_minutes(ll) :
    """
        Note that the proper degrees is abs([0]) * [2], which can be negative zero
    """

    neg     =  1
    if  ll < 0.0 :
        ll  = -ll
        neg = -1

    m       = (float(ll) - float(int(ll))) * 60.0

    if  neg < 0 :
        ll  = -ll

    return([ int(ll), m, neg ] )


def lat_lon_degrees_minutes_string(ll, plus, ne, sw) :

    #   This returns a string up to 14 or 15 characters in length

    dr          = ""
    if  plus   == None :
        dr      = ne
        if  ll  < 0.0 :
            ll  = -ll
            dr  = sw
        plus    = ""
    elif plus  != "+" :
        plus    = ""

    dm          =   lat_lon_in_degrees_minutes(ll)

    if  (not dr) and (dm[0] >= 0) and (dm[2] < 0) :           # special case of when the degrees are zero to the west or south and he asked for a +- format
        return(("  -0 %8.6f'%s") % ( dm[1], dr ) )

    return(("%" + plus + "4i %8.6f'%s") % ( dm[0], dm[1], dr ) )


def lat_degrees_minutes_string(lat, plus = None) :
    return(lat_lon_degrees_minutes_string(lat, plus, " N", " S"))

def lon_degrees_minutes_string(lon, plus = None) :
    return(lat_lon_degrees_minutes_string(lon, plus, " E", " W"))






def lat_lon_in_degrees_minutes_seconds(ll) :
    """
        Note that the proper degrees is abs([0]) * [3], which can be negative zero
    """

    neg     =  1
    if  ll < 0.0 :
        ll  = -ll
        neg = -1

    m       = (float(ll) - float(int(ll))) * 60.0
    s       = (float(m)  - float(int(m ))) * 60.0

    if  neg < 0 :
        ll  = -ll

    return([ int(ll), int(m), s, neg ] )


def lat_lon_degrees_minutes_seconds_string(ll, plus, ne, sw) :

    #   This returns a string up to 17 or 16 characters in length

    dr          = ""
    if  plus   == None :
        dr      = ne
        if  ll  < 0.0 :
            ll  = -ll
            dr  = sw
        plus    = ""
    elif plus  != "+" :
        plus    = ""

    dms         =   lat_lon_in_degrees_minutes_seconds(ll)

    if  (not dr) and (dms[0] >= 0) and (dms[3] < 0) :           # special case of when the degrees are zero to the west or south and he asked for a +- format
        return(("  -0 %2u\' %6.3f\"%s") % ( dms[1], dms[2], dr ) )

    return(("%" + plus + "4i %2u\' %6.3f\"%s") % ( dms[0], dms[1], dms[2], dr ) )


def lat_degrees_minutes_seconds_string(lat, plus = None) :
    return(lat_lon_degrees_minutes_seconds_string(lat, plus, " N", " S"))

def lon_degrees_minutes_seconds_string(lon, plus = None) :
    return(lat_lon_degrees_minutes_seconds_string(lon, plus, " E", " W"))




def signed_lat_lon_string(ll) :
    return("%.8f" % ll)



def lat_lon_string(ll, plus, ne, sw) :

    #   This returns a string up to 14 or 13 characters in length

    dr          = ""
    if  plus   == None :
        dr      = ne
        if  ll  < 0.0 :
            ll  = -ll
            dr  = sw
        plus    = ""

    return(("%" + plus + ".8f%s") % ( ll, dr ) )


def lat_string(lat, plus = None) :
    return(lat_lon_string(lat, plus, " N", " S"))

def lon_string(lon, plus = None) :
    return(lat_lon_string(lon, plus, " E", " W"))


def nsew_lat_lon_string(lat, lon) :
    ts  = lat_string(lat)
    os  = lon_string(lon)

    #   kludge - this is what happens when you try to use test routines in real life
    if  (ts[len(ts) - 4:len(ts) - 2] == "00") and (os[len(os) - 4:len(os) - 2] == "00") :
        ts  = ts[0:-4] + ts[len(ts) - 2:]
        os  = os[0:-4] + os[len(os) - 2:]

    return(ts + "  " + os)





def degrees_minutes_seconds_to_lat_lon(d, m, s, sign_mult = 1) :
    if  (d < 0.0) or (sign_mult < 0) :
        m   = -m
        s   = -s

    return(d + (m / 60.0) + (s / 3600.0))






no_dots_re      = re.compile(r"(^|[^\d])\.([^\d]|$)")

#
#               Note: These are used in projects\mapping\*.py and maybe other places
#
dec_rs          = r"(?:\d+\.\d*|\.\d+|\d+)"
plus_min_dec_rs = r"[+-]?" + dec_rs

dec_only_rs     = r"(?:\d*\.\d+)"


dg_sep_rs       = unicode(r"(?:(?:d(?:eg(?:rees?)?)?|\xa7|\xb0|\xba|\xf8),?)", 'latin1')
deg_sep_rs      = r"\s*"       + dg_sep_rs
cln_deg_sep_rs  = r"\s*(?:\:|" + dg_sep_rs + ")"

mn_sep_rs       = r"(?:m(?:in(?:utes?)?)?|')"
min_sep_rs      = r"\s*"       + mn_sep_rs
cln_min_sep_rs  = r"\s*(?:\:|" + mn_sep_rs + ")"

sc_sep_rs       = r'(?:sec(?:onds?)?|")'
sec_sep_rs      = r"\s*"       + sc_sep_rs
cln_sec_sep_rs  = r"\s*(?:\:|" + sc_sep_rs + ")"
s_sec_sep_rs    = r"\s*(?:"    + sc_sep_rs + "|s)"


dms_from_re     = re.compile(r"([+-]?\d+\s*)" + deg_sep_rs + "\s*((?:[nsew])?\s*(?:\d+)\s*)" + min_sep_rs + "\s*(" + dec_rs + ")\s*" + s_sec_sep_rs)
dms_to_rs       = r"\1 deg \2 min \3 sec "

dM_from_re      = re.compile(r"([+-]?\d+\s*)" + deg_sep_rs + "\s*((?:[nsew])?\s*" + dec_only_rs + ")" + min_sep_rs + "(\s*[nsew,:]|$)")
dM_to_rs        = r"\1 deg \2 min 0 sec \3"

dm_from_rs      = r"([^\d]*\d+[^\.][^\d]*)(" + dec_rs + ")" + min_sep_rs + "([^\d]*)"
dm_from_re      = re.compile(r"^" + dm_from_rs + dm_from_rs + "$")
dm_to_rs        = r"\1\2 min 0 sec \3\4\5 min 0 sec \6"

ms_from_re      = re.compile(r"((?:\d+)\s*)m\s*(" + dec_rs + ")\s*" + s_sec_sep_rs)
ms_to_rs        = r"0 deg \1 min \2 sec "


nd_dms_from_re  = re.compile(r"(^|[^\.\d])([+-]?\d{1,3})(\d\d)(\d\d(?:\.\d*)?)")
nd_dms_to_rs    = r"\1\2 deg \3 min \4 sec "

nd_dm_from_re   = re.compile(r"(^|[^\.\d])([+-]?\d{2,3})(\d\d(?:\.\d*)?)")
nd_dm_to_rs     = r"\1\2 deg \3 min "

if  False :
    dms_rs      = r"\s*([%s])?\s*([+-]?\d+)\s*d\s*([%s])?\s*(\d+)\s*m\s*(" + dec_rs + ")\s*s\s*([%s])?"

    dms_ns      = dms_rs % ( "ns", "ns", "ns" )
    dms_ew      = dms_rs % ( "ew", "ew", "ew" )

    dms_ns_re   = re.compile(dms_ns)
    dms_ew_re   = re.compile(dms_ew)

    dms_nsew_re = re.compile(dms_ns + r"\s*[,:]?" + dms_ew)
    dms_ewns_re = re.compile(dms_ew + r"\s*[,:]?" + dms_ns)

order_o_re      = re.compile(r"^(.*)(?<![a-z])([nsew])\b.+?(?<![a-z])([nsew])\b(.*)$")


decimal_rs      = r"\s*([nsew])?\s*(" + plus_min_dec_rs + ")" + cln_deg_sep_rs + "?\s*([nsew])?"

decimal_re      = re.compile(decimal_rs)
value_re        = re.compile(decimal_rs + "(?:\s*(" + dec_rs + ")" + cln_min_sep_rs + "?(?:\s*(" + dec_rs + ")" + cln_sec_sep_rs + "?)?)?\s*([nsew])?")

nsew_re         = re.compile(r"\b[nsew]\b")

split_on_deg_re = re.compile(r"^(.*?(?:" + plus_min_dec_rs + ")?" + deg_sep_rs + ".*?)(" + plus_min_dec_rs + "" + deg_sep_rs + ".*\s*[nsew]?.*)$")

exp_ll_rs       = r"\b%s[a-z]*\s*[=:]?\s*[\"\']?\s*(" + plus_min_dec_rs + ")\s*[\"\']?"

exp_lat_re      = re.compile(exp_ll_rs % ( "la?t" ) )
exp_lon_re      = re.compile(exp_ll_rs % ( "lo?ng?" ) )

geo_re          = re.compile(r"\bgeo:(" + plus_min_dec_rs + "),(" + plus_min_dec_rs + ")")

nmea_re         = re.compile(r"(\d?\d)(\d\d)\.(\d+)\s*,\s*(N|S)\s*,\s*(\d?\d?\d)(\d\d)\.(\d+)\s*,\s*(E|W)")

xyz_re          = re.compile(r"\bX\s*=\s*(" + plus_min_dec_rs + r")\s*Y\s*=\s*(" + plus_min_dec_rs + r")\s*Z\s*=\s*(" + plus_min_dec_rs + ")", re.IGNORECASE)



def fix_lat_lon(lat, lon, order = None) :
    if  (lat == None) or (lat == None) :
        return( [ None, None ] )

    if  order == 2 :                            # lon/lat order
        ( lat, lon )    = ( lon, lat )
    elif order == None :                        # figure out the order
        if  not ( -90.0 <= lat <= 90.0) :
            ( lat, lon )    = ( lon, lat )
        pass

    if  not ( -90.0 <= lat <= 90.0) :
        return( [ None, None ] )

    if  -360.0 <= lon <  -180.0 :
        lon     = lon +   360.0                 # take Far Far Eastern Airlines (note: do not use lon_in_world_modulo() here - we're trying to ignore lon numbers that are not longitude numbers)

    if   180.0 <  lon <=  360.0 :
        lon    -=         360.0                 # go west, young man

    if  not (-180.0 <= lon <= 180.0) :
        return( [ None, None ] )

    return( [ lat, lon ] )



def string_degrees_minutes_seconds_to_lat_lon(degs, mins, secs) :
        sm      =  1
        if  degs and (degs[0] == '-') :
            sm  = -1

        return(degrees_minutes_seconds_to_lat_lon(float(degs), float(mins or 0.0), float(secs or 0.0), sm))


def lat_lon_value_parse(s) :
    g   = value_re.match(s)

    if  g :
        nsew    = (g.group(1) or "") + (g.group(3) or "") + (g.group( 6) or "")
        ll      = string_degrees_minutes_seconds_to_lat_lon(g.group(2), g.group( 4), g.group( 5))

        if  not nsew :
            return( ( ll, None ) )

        if  len(nsew) == 1 :
            if  (ll > 0.0) and ((nsew  == "s") or (nsew == 'w')) :
                ll  = -ll

            if  (nsew  == "n") or (nsew == 's') :
                return( ( ll, 1 ) )

            return( ( ll, 2 ) )

        return( ( ll, None ) )


    return( ( None, None ) )



def _g2_things(g) :
    nsew    = (g.group(1) or "") + (g.group(3) or "") + (g.group( 6) or "") + (g.group(7) or "") + (g.group(9) or "") + (g.group(12) or "")
    lat     = string_degrees_minutes_seconds_to_lat_lon(g.group(2), g.group( 4), g.group( 5))
    lon     = string_degrees_minutes_seconds_to_lat_lon(g.group(8), g.group(10), g.group(11))

    return( ( nsew, lat, lon ) )



front_nsew_re   = re.compile(r"^([nsew].*?)(\b[nsew]\b)\s*$")
def _fix_front_nsew(v1, v2) :

    g       = front_nsew_re.match(v1)
    if  g :
        v1  = g.group(1)
        v2  = g.group(2) + v2

    return( ( v1, v2 ) )


def _2_values(v1, v2, order) :
    ( lat, ao )  = lat_lon_value_parse(v1)
    if  lat != None :
        ( lon, oo )  = lat_lon_value_parse(v2)
        if  lon != None :
            if  order == None :
                if  (ao == 2) or (oo == 1) :
                    order   = 2
                pass
            ( lat, lon )    = fix_lat_lon(lat, lon, order)
            if  lat != None :
                return( [ lat, lon ] )
            pass
        pass

    return( ( None, None ) )



def _2_sorta_values(s, order) :
    g   = value_re.match(s)
    if  g :
        e   = g.end(0)
        if  (g.group(1) or "") and ((g.group(3) or "") + (g.group(6) or "")):
            e       = g.end(2)
        elif  ((g.group(1) or "") + (g.group(3) or "")) and g.group(6) :
            e       = g.end(5)

        ss          = s[g.start(0):e]
        ( lat, ao ) = lat_lon_value_parse(ss)
        if  lat != None :
            ( lat, lon )    = _2_values(ss, s[e:], order)
            if  lat != None :
                return( [ lat, lon ] )
            pass

        pass

    return( [ None, None ] )



def _parse_lat_lon(s) :
    """
        Parse a string that may have a latitude longitude or vice versa in it.

        Return an array [ lat, lon ] or [ None, None ].
    """

    g   = nmea_re.search(s)
    if  g :
        lat         = int(g.group(1)) + ((int(g.group(2)) + (int((g.group(3) + "000")[0:4]) / 10000.0)) / 60.0)
        if  g.group(4).upper() == 'S' :
            lat     = -lat

        lon         = int(g.group(5)) + ((int(g.group(6)) + (int((g.group(7) + "000")[0:4]) / 10000.0)) / 60.0)
        if  g.group(8).upper() == 'W' :
            lon     = -lon

        return( [ lat, lon ] )


    g   = geo_re.search(s)
    if  g :
        ( lat, lon )    = fix_lat_lon(float(g.group(1)), float(g.group(2)))
        if  lat != None :
            return( [ lat, lon ] )
        pass

    s       = s.lower()

    g       = xyz_re.search(s)
    if  g   :
        return(convert_x_y_z_to_lat_lon(float(g.group(1)), float(g.group(2)), float(g.group(3))))


    while True :
        ns  = no_dots_re.sub(r"\1 \2", s)
        if  ns == s :
            break
        s   = ns

    s       = s.strip(",")

    #
    #   Someone used stars for degree characters
    #
    s       = s.replace(u"*", unicode('\xb0', 'latin1'))

    s       = s.replace(u"&amp;#730;", unicode("\xb0", 'latin1'))    # doesn't seem to be needed (ah. the unk report that had this was for a bad-in-other-way value)

    #
    #   Parse out explicit "lat=" and "lon=" and "lat:" and "lon:"
    #
    lag     = exp_lat_re.search(s)
    if  lag :
        lng = exp_lon_re.search(s)
        if  lng :
            ( lat, lon )    = fix_lat_lon(float(lag.group(1)), float(lng.group(1)), 1)
            if  lat != None :
                return( [ lat, lon ] )
            pass
        pass

    if  False :
        sst = dm_from_re.sub(dm_to_rs, s)
        if  sst != s :
            print "diff"
            print "   ", s
            print "   ", sst
        pass

    s   =     dm_from_re.sub(    dm_to_rs, s)
    s   =    dms_from_re.sub(   dms_to_rs, s)
    s   =     ms_from_re.sub(    ms_to_rs, s)
    s   =     dM_from_re.sub(    dM_to_rs, s)

    if  len(nd_dms_from_re.findall(s)) == 2 :
        s   = nd_dms_from_re.sub(nd_dms_to_rs, s)
    if  len(nd_dm_from_re.findall(s))  == 2 :
        s   = nd_dm_from_re.sub(nd_dm_to_rs, s)


    s   = re.sub(r"(?<![a-z])n[a-z]*", "north", s)
    s   = re.sub(r"(?<![a-z])e[a-z]*", "east", s)
    s   = re.sub(r"(?<![a-z])w[a-z]*", "west", s)
    # s   = re.sub(r"(?<![a-z])s[a-z]*", "south", s)        # note: sec isn't south

    s   = re.sub(r"(?<![a-z])no(r(th?)?)?(?![a-z])",   "n", s)
    s   = re.sub(r"(?<![a-z])so(u(th?)?)?(?![a-z])",   "s", s)
    s   = re.sub(r"(?<![a-z])ea(st?)?(?![a-z])",       "e", s)
    s   = re.sub(r"(?<![a-z])we(st?)?(?![a-z])",       "w", s)

    if  False :
        #
        #
        #   Problem is that the "s" in "12d 34m 56s" can be confused with the "s" in "South"
        #
        #
        g           = dms_nsew_re.match(s)
        if  g :
            ( nsew, lat, lon )  = _g2_things(g)
            if  len(nsew) < 3 :
                i       = 0

                if  i < len(nsew) :
                    if  nsew[i] == 's' :
                        if  lat > 0.0 :
                            lat = -lat
                        i  += 1
                    elif nsew[i] == 'n' :
                        i  += 1
                    pass

                if  i < len(nsew) :
                    if  nsew[i] == 'w' :
                        if  lon > 0.0 :
                            lon = -lon
                        i  += 1
                    elif nsew[i] == 'e' :
                        i  += 1
                    pass

                if  i >= len(nsew) :
                    ( lat, lon ) = fix_lat_lon(lat, lon, 1)
                    if  lat != None :
                        return( [ lat, lon ] )
                    pass

                pass

            pass

        #
        #   Try #d #m #s in lon lat order
        #
        g           = dms_ewns_re.match(s)
        if  g :
            ( ewns, lon, lat )  = _g2_things(g)

            if  len(ewns) < 3 :
                i       = 0

                if  i < len(ewns) :
                    if  ewns[i] == 'w' :
                        if  lon > 0.0 :
                            lon = -lon
                        i  += 1
                    elif ewns[i] == 'e' :
                        i  += 1
                    pass

                if  i < len(ewns) :
                    if  ewns[i] == 's' :
                        if  lat > 0.0 :
                            lat = -lat
                        i  += 1
                    elif ewns[i] == 'n' :
                        i  += 1
                    pass

                if  i >= len(ewns) :
                    ( lat, lon ) = fix_lat_lon(lat, lon, 1)
                    if  lat != None :
                        return( [ lat, lon ] )
                    pass

                pass
            pass
        pass


    #
    #
    #   Let's get the order, lat/lon or lon/lat, if we can, easily
    #
    #
    order   = None
    g       = order_o_re.match(s)
    if  g :
        if  (not nsew_re.search(g.group(1))) and (not nsew_re.search(g.group(4))) :
            m1  = g.group(2)
            m2  = g.group(3)
            if    ((m1 == 'n') or (m1 == 's')) and ((m2 == 'e') or (m2 == 'w')) :
                order   = 1
            elif  ((m1 == 'e') or (m1 == 'w')) and ((m2 == 'n') or (m2 == 's')) :
                order   = 2
        pass



    #
    #
    #   Maybe the degrees numbers can be distinguished to help us split the two values apart
    #
    #
    g   = split_on_deg_re.match(s)
    if  g :
        # print "split"
        ( lat, lon )    = _2_sorta_values(s, order)
        if  lat != None :
            # print "2split", s, order
            return( [ lat, lon ] )

        # print "not 2sorta", s, order, g.group(1), g.group(2)
        ( v1, v2 )      = _fix_front_nsew(g.group(1), g.group(2))

        # print "v1", v1, "v2", v2

        ( lat, lon )    = _2_values(v1, v2, order)
        if  lat != None :
            return( [ lat, lon ] )
        pass


    #
    #
    #   If he put a single comma or / or \ or ; in, we can easily split the lat and lon apart
    #
    #
    sa  = [ vs for vs in re.split(r"[;,\\/][;,\\/\s]*", s) if vs ]
    if  len(sa) == 2 :
        ( lat, lon )    = _2_values(sa[0], sa[1], order)
        if  lat != None :
            return( [ lat, lon ] )
        pass

    s   =   s.replace(",", " ")
    s   =   s.replace(";", " ")


    if  False :
        #
        #
        #   Maybe we can find a single #d #m #s value somewhere (we'll do it first because of the ambiguity of 's')
        #
        #
        g   = dms_ns_re.search(s)
        if  g :
            # extract what's before and after the #d#m#s value
            v1  = s[0 : g.start()]
            v2  = s[g.end() : ]

            ( lat, lon )    = _2_values(v1, v2, order)
            if  lat != None :
                return( [ lat, lon ] )
            pass

        g   = dms_ew_re.search(s)
        if  g :
            # extract what's before and after the #d#m#s value
            v1  = s[0 : g.start()]
            v2  = s[g.end() : ]

            ( lat, lon )    = _2_values(v2, v1, order)
            if  lat != None :
                return( [ lat, lon ] )
            pass
        pass




    #
    #   See about just two numbers (we'll go with the order as lat lon, but will recognize one of 'em being <-90.0 or >90.0 to fix that)
    #
    va  = [ vs  for vs in re.split(r"\s", s) if vs ]
    if  len(va) == 2 :
        ( lat, lon )    = _2_values(va[0], va[1], order)
        if  lat != None :
            return( [ lat, lon ] )
        pass



    ( lat, lon )        = _2_sorta_values(s, order)
    if  lat != None :
        return( [ lat, lon ] )


    g   = decimal_re.match(s)
    if  g :
        ss          = s[g.start(0):g.end(0)]
        ( lat, ao ) = lat_lon_value_parse(ss)
        if  lat != None :
            ( lat, lon )    = _2_values(ss, s[g.end(0):], order)
            if  lat != None :
                return( [ lat, lon ] )
            pass

        pass


    return( [ None, None ] )


def parse_lat_lon(s) :
    """
        Parse a string that may have a latitude longitude or vice versa in it.

        Return an array [ lat, lon ] or [ None, None ].
    """

    ( lat, lon )            = _parse_lat_lon(s)
    if  lat                == None :
        s                   = re.sub(r"(?i)(?<![a-z])([ns][a-z]*)(.*?)(?<![a-z])([ew][a-z]*)", r"\3\2\1", s)
        ( lat, lon )        = _parse_lat_lon(s)
        if  lat            == None :
            s               = re.sub(r"(?i)(?<![a-z])([ew][a-z]*)(.*?)(?<![a-z])([ns][a-z]*)", r"\3\2\1", s)
            ( lat, lon )    = _parse_lat_lon(s)
        pass

    return( [ lat, lon ] )





#
#
#   Test code.
#
#
if __name__ == '__main__' :
    import  TZCommandLineAtFile

    del(sys.argv[0])

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    test_parse  = True


    ############################################################
    def lls(lat, lon, plus) :
        ds      = "%14s %14s" % ( lat_string(                        lat, plus = plus), lon_string(                        lon, plus = plus) )
        dmss    = "%17s %17s" % ( lat_degrees_minutes_seconds_string(lat, plus = plus), lon_degrees_minutes_seconds_string(lon, plus = plus) )
        dms     = "%14s %14s" % ( lat_degrees_minutes_string(        lat, plus = plus), lon_degrees_minutes_string(        lon, plus = plus) )
        return( [ ds, dmss, dms ] )

    def show_latlon(vs, lat, lon) :

        ( ds, dmss, dms )   = lls(lat, lon, None)
        print ("%-62s %s   %s" % ( vs, ds, dmss )).encode('utf8')
    ############################################################

    while len(sys.argv) :
        test_parse  = False

        lsa         = sys.argv.pop(0)

        if  not sys.argv :
            ( lat, lon )    = parse_lat_lon(lsa)
            if  lat == None :
                print "NO PARSE", lsa
            else :
                show_latlon(lsa, lat, lon)
            break

        lso         = sys.argv.pop(0)

        ( lat1, lon1 )  = parse_lat_lon(lsa)
        ( lat2, lon2 )  = parse_lat_lon(lso)

        if  lat2 == None :
            d               = float(lso)
            ( lat2, lon2 )  = distance_in_lat_lon_here(lat1, lon1, d)
            lat2           += lat1
            lon2           += lon1

        print "From %.6f, %.6f to %.6f, %.6f :" % ( lat1, lon1, lat2, lon2 )

        d       = calcDistance(lat1, lon1, lat2, lon2)
        print   "%11.2f nautical miles" % ( d )

        d      *= milesPerNauticalMile
        print   "%11.2f miles" % ( d )

        d      *= 5280
        print   "%11.2f feet" % ( d )
    pass



    va  = [
        " 47 deg 17' 49.23\" N, 121 deg 45' 57.33\" W ",
        " 121 deg 45' 57.33\" W, 47 deg 17' 49.23\" N ",
        " 121 deg 45' 57.\" W, 47 deg 17' 49.\" N ",
        " 121 deg 45' 57.\"W, 47 deg 17' 49.\"N ",

        " 31.243333,-7.989808 ",
        " 31.243333/-7.989808 ",
        " 31.243333\\-7.989808 ",
        " 31.243333  / / -7.989808 ",
        " 31.243333\\ \\ \\-7.989808 ",
        " 31.12345N,     65.948862W ",
        " 65.948862W, 31.12345N     ",
        " 31.12345s,     65.948862e ",
        " 65.948862e, 31.12345s     ",
        "31N16 21,      65W58 36",
        "31.12345N,     65.948862W",
        "31.123456,     -65.948862",

        " 172 deg 25 min 55 sec E 34 degrees 3  minutes 2.1 seconds N",
        " 172 deg 25 min 55 sec W  S 34 degrees 3  minutes 2.1 seconds ",

        "71d 25m 55s, 134 d 3 m 2 s WEST",
        "71d 25m 55s NORTH 134 d 3 m 2 s EAST",
        "71d 25m 55s SOUTH 134 d 3 m 2 s",
        "-71d 25m 55s SOUTH -134 d 3 m 2 s",

        "71.43194444 S 134.05055556 W",
        "71.S 134. W",
        "71. S 134.W",


        "134 d 3 m 2 s WEST, 71d 25m 55s",
        "134 d 3 m 2 s EAST 71d 25m 55s NORTH",
        "134 d 3 m 2 s 71d 25m 55s SOUTH ",
        "-134 d 3 m 2 s-71d 25m 55s SOUTH ",

        "31:13:46N,     65:56:55W",
        "31:45:46.302N, 65:56:55.903W",

        unicode("12.25\xb0 E -0.25\xb0 S", 'latin1'),
        "-0 15'  0.000\"    12 15'  0.000\"",

        unicode("0.00901\xb0 0\xf8 1' 23\"", 'latin1'),

        unicode("+11\xb0 11' 11.00\", -1\xf8 2' 3.45\"", 'latin1'),

        unicode("31\37034'21\"N,   65\26058'36\"W", 'latin1'),
        unicode("N45\xf8 26.7717', W65\xb0 56.93172'", 'latin1'),

        unicode("S12.25\xf8  W -0.25\xb0", 'latin1'),
        "-12 15'  0.000\"    -0 15'  0.000\"",

        unicode("-13\xb0 0', 80 59'", 'latin1'),

        "34deg 45min 3sec N, 132deg west",

        "41.23235 -122.123124",
        " -41.23235   122.123124  ",

        "-122.123124   41.23235 ",
        " 122.123124 -41.23235     ",

        " ln =  122.123124 latitude=-41.23235     ",
        "lon:-122.123124 lt:-41.23235",
        "lng:-122.123124 lt:-41.23235",
        "longitudanal  :-122.123124 ltitudinal :-41.23235",

        'lat="50.901697" lon="-1.489356"',
        "N 36* 48.5' W 117* 46.9'",
        "  x=-0.37318314  y= -0.70835411 z =0.59914005  ",
        "34.07757008999999;-118.474374396 ",
        "624826.14 n 1450350.56 w",
        "-620000.00 n -1450000.0000",
        "-620000 n -1450000",
        "-620000. n -1450000.",
        "-6200.00 n -14500.0000",
        "-6200 n -14500 ",
        "-6200. n -14500.",

        "23 deg 17 min",
        "23.5",
        "123.5",
        "lon=123.5",
        "lat=23.5",
        "lon=23.5",

        "55 0\' 0.000\" N, 12 0\' 0.000\" E",               # cgi output
        "55 0\' 0.002\" N 12 0\' 0.001\" w",                # old cgi output
        "12 0\' 0.002\" w 55 0\' 0.001\" s",
        "12 0\' 0.002\" e 55 0\' 0.001\" s",
        "12 0\' 0.002\" e 55 0\' 0.001\" n",

        " 172 25' 55\" E 34 3' 2.1\" N",
        " 172 25' 55\" W  S 34 3' 2.1\" ",

        unicode("131\xb0 13' 14 21 32 43", 'latin1'),

        unicode("10\xb0 0.05' 20\xb0 30 40", 'latin1'),

        " .0 deg 17' 49.23\" N, .0 deg 45' 57.33\" W ",
        " .0 deg 17'   .23\" N, .0 deg 45'   .33\" W ",
        " .0 deg 17' 49.\"   N, .0 deg 45' 57.  \" W ",
        unicode("S12.25\xf8  W -.25\xb0", 'latin1'),
        unicode("S12.25\xf8  W +.25\xb0", 'latin1'),
        unicode("S.25\xf8    W +.25\xb0", 'latin1'),

        "71d 25m 55s, 134 3 2",

        "172d 25m 55s E 34 3 2 N",
        "25m 55s E 34 3 2 N",
        "25m 55sw 3m 2ss",
        "25m 55se 3m 2sn",

        "-290  34",
        " 290  34",
        " 360  90",
        "-360  90",
        "-360 -90",

        unicode("  48 degrees. 12.0556 min n    16 \xb0. 22.1729 min e ", 'latin1'),
        ". 48 degrees. 12.0556 min no   16 deg.  22.1729 min e.",

        "  48 degrees  12.0556 min nor  16 deg,  22.1729 min e ",
        unicode("  48 deg,     12.0556 min nort 16 \xf8  22.1729 min e ", 'latin1'),
        "  48 degrees, 12.0556 min n    16 deg,  22.1729 min e ",
        ", 48 degrees  12.0556 min n    16 deg,  22.1729 min e ",
        ", 48 degrees, 12.0556 min n    16 deg   22.1729 min e ",
        ", 48 degrees, 12.0556 min n    16 deg,  22.1729 min e ",
        "  48 degrees  12.0556 min n    16 deg,  22.1729 min e,",
        "  48 degrees, 12.0556 min n    16 deg   22.1729 min e,",
        "  48 degrees, 12.0556 min n    16 deg,  22.1729 min e,",
        ", 48 degrees  12.0556 min n    16 deg,  22.1729 min e,",
        ", 48 degrees, 12.0556 min n    16 deg   22.1729 min e,",
        ", 48 degrees, 12.0556 min n    16 deg,  22.1729 min e,",

        " geo:-48.345,180?blah=dj&to=l%20kjsdf",
        " geo:-48.345,180,234?blah=dj&to=l%20kjsdf",
        "geo:-48.345,180",
        "geo:+148.345,+80,234",

        ";  The ones with south in them don't work correctly - sec problem",
        " 100nORTH 10Western",
        " 100nwe 10wESTERN",
        " 100sORTH 10Eestern",
        " 100swe 10eESTERN",
        " 10Western 100nORTH ",
        " 10wESTERN 100nwe ",
        " 10Eestern 100sORTH ",
        " 10eESTERN 100swe ",

        " 100nORTH , 10Western",
        " 100nwe , 10wESTERN",
        " 100sORTH , 10Eestern",
        " 100swe , 10eESTERN",
        " 10Western , 100nORTH ",
        " 10wESTERN , 100nwe ",
        " 10Eestern , 100sORTH ",
        " 10eESTERN , 100swe ",

        " 100 nORTH 10 Western",
        " 100 nwe 10 wESTERN",
        " 100 sORTH 10 Eestern",
        " 100 swe 10 eESTERN",
        " 10 Western 100 nORTH ",
        " 10 wESTERN 100 nwe ",
        " 10 Eestern 100 sORTH ",
        " 10 eESTERN 100 swe ",

        " 100 nORTH , 10 Western",
        " 100 nwe , 10 wESTERN",
        " 100 sORTH , 10 Eestern",
        " 100 swe , 10 eESTERN",
        " 10 Western , 100 nORTH ",
        " 10 wESTERN , 100 nwe ",
        " 10 Eestern , 100 sORTH ",
        " 10 eESTERN , 100 swe ",


        "",
        ";  ambiguous 20, should be longitude",
        unicode("10\xb0 0.05' 20 30 40", 'latin1'),

        ]

    if  False :
        for vs  in va :

            g   = value_re.match(vs)
            if  not g :
                print "NO VALUE", vs
            else :
                print vs, "------",
                for i in xrange(1, g.lastindex + 1) :
                    print "|", g.group(i) or "*",
                print   "----------------------", vs[g.end():]
            pass
        pass

    print


    if  False :

        llvs    = [ 0, 0.0, 0.7, 2, 2.1, 45.0,
                        89.0,  89.3,  90.0,  90.3,  91.0,
                       179.0, 179.3, 180.0, 180.3, 181.0,
                       269.0, 269.3, 270.0, 270.3, 271.0,
                       359.0, 359.3, 360.0, 360.3, 361.0,
                       449.0, 449.3, 450.0, 450.3, 451.0,
                       539.0, 539.3, 540.0, 540.3, 541.0,
                       629.0, 629.3, 630.0, 630.3, 631.0,
                       719.0, 719.3, 720.0, 720.3, 721.0,
                  ]

        print
        print "lat"
        for ll in llvs :

            print
            print "lat     %7.2f" % ( float( ll) ),
            print "%7.2f %7.2f" % ( float(lat_in_world( ll)), float(lat_in_world_modulo( ll)) )

            if  not ( -90 <= lat_in_world_modulo(ll) <=  90) :
                raise "Bad lat %7.2f" % (  lat_in_world_modulo(ll) )
            if  not (-180 <= lon_in_world_modulo(ll) <= 180) :
                raise "Bad lon %7.2f" % (  lon_in_world_modulo(ll) )
            pass

        print
        print "neg lat"
        for ll in llvs :

            print
            print "lat     %7.2f" % ( float(-ll) ),
            print "%7.2f %7.2f" % ( float(lat_in_world(-ll)), float(lat_in_world_modulo(-ll)) )

            if  not ( -90 <= lat_in_world_modulo(ll) <=  90) :
                raise "Bad lat %7.2f" % (  lat_in_world_modulo(ll) )
            if  not (-180 <= lon_in_world_modulo(ll) <= 180) :
                raise "Bad lon %7.2f" % (  lon_in_world_modulo(ll) )
            pass

        print
        print "lon"
        for ll in llvs :

            print
            print "lon     %7.2f" % ( float( ll) ),
            print "%7.2f %7.2f" % ( float(lon_in_world( ll)), float(lon_in_world_modulo( ll)) )

            if  not ( -90 <= lat_in_world_modulo(ll) <=  90) :
                raise "Bad lat %7.2f" % (  lat_in_world_modulo(ll) )
            if  not (-180 <= lon_in_world_modulo(ll) <= 180) :
                raise "Bad lon %7.2f" % (  lon_in_world_modulo(ll) )
            pass

        print
        print "neg lon"
        for ll in llvs :

            print
            print "lon     %7.2f" % ( float(-ll) ),
            print "%7.2f %7.2f" % ( float(lon_in_world(-ll)), float(lon_in_world_modulo(-ll)) )


            if  not ( -90 <= lat_in_world_modulo(ll) <=  90) :
                raise "Bad lat %7.2f" % (  lat_in_world_modulo(ll) )
            if  not (-180 <= lon_in_world_modulo(ll) <= 180) :
                raise "Bad lon %7.2f" % (  lon_in_world_modulo(ll) )
            pass
        pass


    if  test_parse :

        for vs  in va :
            if  not vs :
                print
            elif vs.lstrip()[0] == ';' :
                print
                print vs
            else :
                ( lat, lon )    = parse_lat_lon(vs)

                if  lat == None :
                    ( lat, lon )    = parse_lat_lon(vs + " , lon=-179.99999")
                    if  lat != None :
                        if  abs(lon - -179.99999) >= 0.0001 :
                            lat     = None
                        else        :
                            lon     = 0.0
                        pass
                    pass

                if  lat == None :
                    ( lat, lon )    = parse_lat_lon(vs + " , lat=-89.99999")
                    if  lat != None :
                        if  abs(lat - -89.99999) >= 0.0001 :
                            lat     = None
                        else        :
                            lat     = 0.0
                        pass
                    pass

                if  lat == None :
                    ( lat, lon )    = parse_lat_lon(vs + " , -179.99999")
                    if  lat != None :
                        if  abs(lon - -179.99999) >= 0.0001 :
                            lat     = None
                        else        :
                            lon     = 0.0
                        pass
                    pass

                if  lat == None :
                    ( lat, lon )    = parse_lat_lon(vs + " , 89.99999North")
                    if  lat != None :
                        if  abs(lat - 89.99999) >= 0.0001 :
                            lat     = None
                        else        :
                            lat     = 0.0
                        pass
                    pass

                if  lat == None :
                    print "NO PARSE", vs.encode('utf8')
                else :

                    show_latlon(vs, lat, lon)



                    def check_output_s(lat, lon, plus, s) :
                        ( la, lo )      = parse_lat_lon(s)
                        if  la == None :
                                print "Error plus =", plus, "lat=", lat, la, '[' + s + ']'
                        else :
                            if  round(la  * 1000000.0) != round(lat * 1000000.0) :
                                print "Error plus =", plus, "lat=", lat, la, '[' + s + ']'
                            if  round(lo  * 1000000.0) != round(lon * 1000000.0) :
                                print "Error plus =", plus, "lon=", lon, lo, '[' + s + ']'
                            pass
                        pass

                    def check_output(lat, lon, plus) :
                        ( ds, dmss, dms )   = lls(lat, lon, plus)
                        check_output_s(lat, lon, plus, ds)
                        check_output_s(lat, lon, plus, dmss)
                        check_output_s(lat, lon, plus, dms)

                    check_output(lat, lon, None)
                    check_output(lat, lon, "")
                    check_output(lat, lon, "+")

                pass
            pass
        pass

        for vs  in va :
            if  not vs :
                pass
            elif vs.lstrip()[0] == ';' :
                pass
            else :
                pass
            pass
        pass


    lls = [
            [  -1.2,   -1.2 ],
            [  -1.2,    0.0 ],
            [  -1.2,    0.3 ],
            [  -1.2,   10.0 ],
            [  -1.2,  -10.0 ],
            [  -1.2,  -89.2 ],
            [  -1.2,  -90.0 ],
            [  -1.2,  -91.3 ],
            [  -1.2, -178.2 ],
            [  -1.2, -180.0 ],
            [  -1.2,  180.0 ],
            [  -1.2,  178.7 ],
            [  -1.2,   91.3 ],
            [  -1.2,   90.0 ],
            [  -1.2,   89.2 ],

            [   0.0,   -1.2 ],
            [   0.0,    0.0 ],
            [   0.0,    1.3 ],
            [   0.0,  -89.2 ],
            [   0.0,  -90.0 ],
            [   0.0,  -91.3 ],
            [   0.0, -178.2 ],
            [   0.0, -180.0 ],
            [   0.0,  180.0 ],
            [   0.0,  178.7 ],
            [   0.0,   91.3 ],
            [   0.0,   90.0 ],
            [   0.0,   89.2 ],

            [  -1.2,   -1.2 ],
            [  -1.2,    0.0 ],
            [  -1.2,    0.3 ],
            [  -1.2,   10.0 ],
            [  -1.2,  -10.0 ],
            [  -1.2,  -89.2 ],
            [  -1.2,  -90.0 ],
            [  -1.2,  -91.3 ],
            [  -1.2, -178.2 ],
            [  -1.2, -180.0 ],
            [  -1.2,  180.0 ],
            [  -1.2,  178.7 ],
            [  -1.2,   91.3 ],
            [  -1.2,   90.0 ],
            [  -1.2,   89.2 ],

            [ -30.2,   -1.2 ],
            [ -30.2,    0.0 ],
            [ -30.2,    0.3 ],
            [ -30.2,   10.0 ],
            [ -30.2,  -10.0 ],
            [ -30.2,  -89.2 ],
            [ -30.2,  -90.0 ],
            [ -30.2,  -91.3 ],
            [ -30.2, -178.2 ],
            [ -30.2, -180.0 ],
            [ -30.2,  180.0 ],
            [ -30.2,  178.7 ],
            [ -30.2,   91.3 ],
            [ -30.2,   90.0 ],
            [ -30.2,   89.2 ],

            [ -89.2,   -1.2 ],
            [ -89.2,    0.0 ],
            [ -89.2,    0.3 ],
            [ -89.2,   10.0 ],
            [ -89.2,  -10.0 ],
            [ -89.2,  -89.2 ],
            [ -89.2,  -90.0 ],
            [ -89.2,  -91.3 ],
            [ -89.2, -178.2 ],
            [ -89.2, -180.0 ],
            [ -89.2,  180.0 ],
            [ -89.2,  178.7 ],
            [ -89.2,   91.3 ],
            [ -89.2,   90.0 ],
            [ -89.2,   89.2 ],

            [ -90.0,   -1.2 ],
            [ -90.0,    0.0 ],
            [ -90.0,    0.3 ],
            [ -90.0,   10.0 ],
            [ -90.0,  -10.0 ],
            [ -90.0,  -89.2 ],
            [ -90.0,  -90.0 ],
            [ -90.0,  -91.3 ],
            [ -90.0, -178.2 ],
            [ -90.0, -180.0 ],
            [ -90.0,  180.0 ],
            [ -90.0,  178.7 ],
            [ -90.0,   91.3 ],
            [ -90.0,   90.0 ],
            [ -90.0,   89.2 ],

            [  30.2,   -1.2 ],
            [  30.2,    0.0 ],
            [  30.2,    0.3 ],
            [  30.2,   10.0 ],
            [  30.2,  -10.0 ],
            [  30.2,  -89.2 ],
            [  30.2,  -90.0 ],
            [  30.2,  -91.3 ],
            [  30.2, -178.2 ],
            [  30.2, -180.0 ],
            [  30.2,  180.0 ],
            [  30.2,  178.7 ],
            [  30.2,   91.3 ],
            [  30.2,   90.0 ],
            [  30.2,   89.2 ],

            [  89.2,   -1.2 ],
            [  89.2,    0.0 ],
            [  89.2,    0.3 ],
            [  89.2,   10.0 ],
            [  89.2,  -10.0 ],
            [  89.2,  -89.2 ],
            [  89.2,  -90.0 ],
            [  89.2,  -91.3 ],
            [  89.2, -178.2 ],
            [  89.2, -180.0 ],
            [  89.2,  180.0 ],
            [  89.2,  178.7 ],
            [  89.2,   91.3 ],
            [  89.2,   90.0 ],
            [  89.2,   89.2 ],

            [  90.0,   -1.2 ],
            [  90.0,    0.0 ],
            [  90.0,    0.3 ],
            [  90.0,   10.0 ],
            [  90.0,  -10.0 ],
            [  90.0,  -89.2 ],
            [  90.0,  -90.0 ],
            [  90.0,  -91.3 ],
            [  90.0, -178.2 ],
            [  90.0, -180.0 ],
            [  90.0,  180.0 ],
            [  90.0,  178.7 ],
            [  90.0,   91.3 ],
            [  90.0,   90.0 ],
            [  90.0,   89.2 ],

          ]


    for ll in lls :
        lat         = ll[0]
        lon         = ll[1]

        ( x, y, z ) = convert_lat_lon_to_x_y_z(lat, lon)

        ( ra, ro)   = convert_x_y_z_to_lat_lon(x, y, z)

        # print lat, lon, x, y, z, ra, ro

        if  abs(ra - lat) > 0.0000001 :
            print "goof lat", lat, lon, x, y, z, ra, ro
        if  abs(ro - lon) > 0.0000001 :
            print "goof lon", lat, lon, x, y, z, ra, ro
        pass

    pass



#
#
#
# eof

