#!/usr/bin/python

# fastway.py
#       --copyright--                   Copyright 2008 (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--
#       November 8, 2008        bar
#       November 12, 2008       bar
#       November 15, 2008       bar
#       November 18, 2008       bar
#       December 20, 2008       bar     better comments in kmz file
#       December 21, 2008       bar     re-org for more logic exposure to the outs
#       November 12, 2011       bar     correct url
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       February 4, 2017        bar     --label_near
#       --eodstamps--
##      \file
#       \namespace              tzpython.fastway
#
#
#       Find the fastest/shortest path from X to Y.
#
#
#       TODO:
#
#           Once the routes have been figured out or sometime, stretch the ends to meet. They drop off near the shared point, but not as close as possible to the shared point.
#
#           Do MIN_ROUTE_DISTANCE_DIFF. Don't include route-sets when no sub-route does not veer this far from any other sub-route.
#               Do after extending the ends, if that's a post-processing thing.
#
#           Options to sort sub-routes by distance and time.
#
#           Two routes-sets, each with two paths that each go through the other route-set's starting point.
#               They would eliminate each other if the shared-point logic did things correctly.
#               How to decide which route-set to use?
#               mv_enumclaw_*.gpx
#
#           Make sure the routes continue together after the end points.
#               See that Front St matches with I90 in mv_miller*.gps before the Front St route gets on the freeway.
#               Also, that combination shows the east path matching in both places because of a big point up by the Union station north of the freeway.
#               Why was the one not stripped?
#
#
#           Other comparisons:
#
#               Turn counts
#               Altitude gains
#
#

import  difflib
import  sys
import  time

import  latlon
import  tzlib
import  tz_gps
import  tz_parse_time



POINT_CUTOFF_DISTANCE   =  0.1                      # about 600 feet (1/10th of a nautical mile)

ROUTE_CUTOFF_DISTANCE   =  0.5                      # alternate routes must go at least this far as the crow flies to be interesting
ROUTE_CUTOFF_TIME       =  3.0 * 60.0               # alternate routes must take at least this long or they aren't interesting

MIN_ROUTE_DISTANCE_DIFF =         0.1               # alternate routes must diverge from each other by at least this far to be of interest
MIN_ROUTE_TIME_DIFF     =        30.0               # alternate routes must differ in time at least this long or they aren't of interest

MIN_ROUTE_TIME_MULTIPLE =         1.2               # the slowest must be this much faster than the fastest route

HOLD_STILL_TIME         = 10.0 * 60.0               # and "stop" for this long or longer causes the route to be ignored


class   a_route(object) :

    def __init__(me, frm, to, distance = None, duration = None, track = None, points = None) :
        me.frm          = frm
        me.to           = to
        me.distance     = distance
        me.duration     = duration
        me.track        = track
        me.points       = points    or []
        me.mx_fns       = 32                        # how many characters to use to print the track name


    def __str__(me) :
        if  me.track and me.track.file_name :
            nm      = "%-*s " % ( me.mx_fns, me.track.file_name )
        else :
            nm      = "File name: ???: "

        if  not me.duration :
            return("%sCould not find sub-route." % nm)

        return("%sFrom: %s %s %s  To: %s %s %s  Duration=%s Distance=%-7s" % (
                                                                                nm,
                                                                                latlon.lat_string(me.frm.lat),
                                                                                latlon.lon_string(me.frm.lon),
                                                                                time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(me.frm.when)),
                                                                                latlon.lat_string(me.to.lat),
                                                                                latlon.lon_string(me.to.lon),
                                                                                time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(me.to.when )),
                                                                                tz_parse_time.hmmss_str(me.duration),
                                                                                "%.1f" % ( (me.distance or 0.0) * latlon.milesPerNauticalMile ),
                                                                             )
              )



    #   a_route



class   a_routes(object) :

    def __init__(me, frm = None, to = None, routes = None) :
        routes      = routes or []
        if  not frm :
            if  routes :
                frm = routes[0].frm
            pass
        if  not to  :
            if  routes :
                to  = routes[0].to
            pass

        me.frm      = frm
        me.to       = to
        me.routes   = routes

    pass                # a_routes




def count_routes_tracks_with_points(routes) :
    lr          = 0
    for ra in routes :
        bi      = 0
        for r in ra.routes :
            if  r.track and r.points :
                bi  = 1
                break
            pass
        lr += bi                                                                # count the number of tracks with points

    return(lr)



def set_routes_mx_fns(routes) :
    lfn         = 13

    for ra in routes :
        for r in ra.routes :
            if  r.track.file_name :
                lfn = max(lfn, len(r.track.file_name))                          # calculate the maximum track name width
            pass
        pass

    for ra in routes :
        for r in ra.routes :
            r.mx_fns    = lfn                                                   # set the track name print length so that printouts line up
        pass

    return(lfn)




def cmp_ti_pi(a, b) :
    if  a._track_idx != b._track_idx :
        return(cmp(a._track_idx, b._track_idx))
    return(cmp(a._point_idx, b._point_idx))




def ok_from_to_big_points(fp, tp, route_cutoff_time, points) :
    """ Is this pair of points reasonable to be a from-to pair? """

    if  cmp_ti_pi(fp.points[0], tp.points[-1]) < 0 :

        fpts    = fp.points
        tpts    = tp.points
        fpi     = tpi   = 0

        while fpi < len(fpts) :
            fpp = fp.points[fpi]
            while tpi   < len(tpts) :
                tpp     = tp.points[tpi]
                if  tpp._track_idx > fpp._track_idx :
                    while True :
                        fpi    += 1
                        if  fpi >= len(fpts) :
                            break
                        fpp     = fp.points[fpi]
                        if  fpp._track_idx >= tpp._track_idx :
                            fpi    -= 1
                            break
                        pass
                    break

                if  (cmp_ti_pi(tpp, fpp) > 0) and (tpp.when - fpp.when >= route_cutoff_time) :
                    while fpi   < len(fpts) :
                        fpp     = fp.points[fpi]
                        if  cmp(fpp, tpp) > 0 :
                            break
                        fpi    += 1

                    while fpi   < len(fpts) :
                        fpp     = fp.points[fpi]
                        while tpi   < len(tpts) :
                            tpp     = tp.points[tpi]
                            if  tpp._track_idx > fpp._track_idx :
                                while True :
                                    fpi    += 1
                                    if  fpi >= len(fpts) :
                                        break
                                    fpp     = fp.points[fpi]
                                    if  fpp._track_idx >= tpp._track_idx :
                                        fpi    -= 1
                                        break
                                    pass
                                break
                            if  (cmp_ti_pi(tpp, fpp) > 0) and (tpp.when - fpp.when >= route_cutoff_time) :
                                return(True)

                            tpi    += 1
                        fpi        += 1
                    pass
                tpi    += 1
            fpi        += 1

        pass

    return(False)



def _make_all_track_point_idx_big_points(ppoints) :
    bpoints     = {}
    for pp in ppoints :
        bpoints[id(pp[0])]  = pp[0]
        bpoints[id(pp[1])]  = pp[1]
    bpoints     = bpoints.values()
    bpts        = []
    for bp in bpoints :
        bpts   += [ [ p._track_idx, p._point_idx, bp ] for p in bp.points ]
    bpts.sort()     # lambda a, b : cmp(a[0], b[0]) or cmp(a[1], b[1]))

    return(bpts)


def _write_track_point_idx_points(file_name, tracks, bpts) :
    print "bpts", len(bpts), file_name

    ti          = -1
    pp          = None
    trks        = []
    t           = None
    for pa in bpts :
        if  ti != pa[0] :
            ti  = pa[0]
            t   = tz_gps.a_track(id_num = ti, when = tracks[ti].when, name = tracks[ti].name, description = tracks[ti].description, file_name = tracks[ti].file_name)
            pp  = None
            trks.append(t)
        if  pp != pa[2] :
            pp  = pa[2]
            p   = tracks[ti].points[pa[1]]
            t.points.append(tz_gps.a_point(lat = pp.lat, lon = pp.lon, when = p.when, altitude = p.altitude))
        pass

    tz_gps.write_gps_file(file_name, None, None, None, tz_gps.color_tracks(trks))



def _is_stop_in(stops, fti, fpi, tti, tpi) :
    if  fti != tti :
        raise ValueError
    if  tpi  < tpi :
        raise ValueError

    for s in stops :
        fp  = s[0]
        if  fp._track_idx > tti :
            break

        if  fp._track_idx == fti :
            if  fp._point_idx  >= tpi :
                break
            if  (fp._point_idx >  fpi) and (s[1]._point_idx < tpi) :
                return(True)
            pass
        pass

    return(False)



def find_alternate_routes_in_tracks(tracks, point_cutoff_distance = None, route_cutoff_distance = None, route_cutoff_time = None, min_route_time_diff = None, min_route_time_multiple = None, hold_still_time = None) :
    """
        Find all the pairs of points that are doubled (or more) in the given tracks.
        That is, that have at least two routes between them whether the routes are pretty much the same physical route or not.
    """

    point_cutoff_distance   = point_cutoff_distance     or  POINT_CUTOFF_DISTANCE
    route_cutoff_time       = route_cutoff_time         or  ROUTE_CUTOFF_TIME
    route_cutoff_distance   = route_cutoff_distance     or  ROUTE_CUTOFF_DISTANCE
    min_route_time_diff     = min_route_time_diff       or  MIN_ROUTE_TIME_DIFF
    min_route_time_multiple = min_route_time_multiple   or  MIN_ROUTE_TIME_MULTIPLE
    hold_still_time         = hold_still_time           or  HOLD_STILL_TIME

    c   = tz_gps.a_point_combiner(cutoff_distance = point_cutoff_distance)

    for t in tracks :
        c.combine_near_points(t.points)

    stops   = c.find_hold_still_point_pairs(tracks, hold_still_time)
    # print "stoplen", len(stops)

    if  False :
        trks    = []
        for s in stops :
            t           = tz_gps.a_track(id_num = len(trks))
            t.points    = [ s[0], s[1] ]
            trks.append(t)
        tz_gps.write_gps_file("x.kmz", None, None, None, trks)


    ctracks = c.map_tracks()


    epoints = {}
    for t in ctracks :
        if  len(t.points) >= 2 :
            epoints[id(t.points[ 0])]   = t.points[ 0]
            epoints[id(t.points[-1])]   = t.points[-1]
        pass
    epoints = epoints.values()

    #
    #   Whack end points that don't have enough original points in them (there won't be many)
    #
    for bpi in xrange(len(epoints) - 1, -1, -1) :
        bp  = epoints[bpi]

        bp.points.sort(cmp_ti_pi)

        if  len(bp.points) > 2 :
            # !!!! note that this requires that all the bp.points be in sorted 'when' order (implying that ti:pi is in when order) - and we may strip points that are 'tween tracks.
            bp.points   = [ bp.points[0] ] + [ bp.points[pi] for pi in xrange(1, len(bp.points) - 1) if (bp.points[pi].when - bp.points[pi-1].when >= route_cutoff_time) or (bp.points[pi+1].when - bp.points[pi].when >= route_cutoff_time)  ] + [ bp.points[-1] ]
        if  len(bp.points) < 2 :
            del(epoints[bpi])                                               # since this point was only gone to once, it won't be an alternate route end-point
        pass


    #
    #       Make an array of from-to big_points, distance-filtered
    #
    ppoints = []
    for fp in epoints :
        for tp in epoints :
            if  not tzlib.same_object(fp, tp) :
                if  fp.flat_distance_from(tp) >= route_cutoff_distance :
                    if  ok_from_to_big_points(fp, tp, route_cutoff_time, epoints) :
                        ppoints.append( [ fp, tp ] )
                    pass
                pass
            pass
        pass


    #
    #   Strip pairs that share points somewhere between them on all paths between the pair
    #
    #   Construct an array of id's of all of the big points that are paired off - in _track_idx._point_idx order
    #   For each pair, construct a hash of all the points between the first from-to.
    #       Then, for each subsequent from-to delete any entry in the hash that's not found between the from-to.
    #       If the hash has anything in it at the end, forget the pair.
    #
    bpts        = _make_all_track_point_idx_big_points(ppoints)

    bids        = [ id(pp[2]) for pp in bpts ]
    tis         = [    pp[0]  for pp in bpts ]
    pis         = [    pp[1]  for pp in bpts ]
    ids         = set( [ i for i in bids ] )

    # print "lids", len(ids), len(ppoints)

    npts        = []
    for pa in ppoints :
        allids  = set()
        andids  = ids.copy()

        pc      = 0
        fi      = 0
        mint    = 1000000000000000.0
        maxt    = 0.0
        while True :
            try :
                fi  = bids.index(id(pa[0]), fi)
            except ValueError :
                break

            try :
                ti  = bids.index(id(pa[1]), fi + 1)
            except ValueError :
                break

            while True  :
                try :
                    fi  = bids.index(id(pa[0]), fi + 1, ti)     # find the closest fi to ti
                except ValueError :
                    break
                pass

            if  tis[fi] == tis[ti] :
                if  not _is_stop_in(stops, tis[fi], pis[fi], tis[ti], pis[ti]) :
                    ft_t    = tracks[tis[ti]].points[pis[ti]].when - tracks[tis[fi]].points[pis[fi]].when
                    mint    = min(mint, ft_t)
                    maxt    = max(maxt, ft_t)

                    ftids   = set( [ i for i in bids[fi+1:ti] ] )
                    # print "  ", len(allids), len(andids), len(ftids), fi, ti, bids[fi+1:ti][0:5]
                    allids  = allids.union(ftids)
                    andids  = andids.intersection(ftids)

                    pc += 1
                pass

            fi  = ti + 1

        # print "pc", pc, len(allids), len(andids), mint, maxt, maxt / (mint or 1.0)
        if  pc >= 2 :
            if  (mint >= route_cutoff_time) and (maxt - mint >= min_route_time_diff) and (maxt >= mint * min_route_time_multiple) :
                if  not allids.intersection(andids) :
                    # print "ok"
                    npts.append(pa)
                pass
            pass
        pass
    ppoints     = npts


    if  False :
        print "lenppoints", len(ppoints), len(ids)
        for pp in ppoints :
            print "--From",
            print   pp[0]
            print "  To  ",
            print   pp[1]
            print "  dist", "%.2f" % pp[0].flat_distance_from(pp[1])
        _write_track_point_idx_points("x.kmz", tracks, _make_all_track_point_idx_big_points(ppoints))


    return(ppoints, stops)





def find_from_to_route_in_track(t, frm, to, stops = None, cutoff_distance = POINT_CUTOFF_DISTANCE) :

    #   Instead of watching when the tracks leave the shared starting point,
    #     note when they start moving away from each other but are still near the starting point or are both outside the starting point's circle.
    #   idea is to find the real, shared starting point, not the point where they truely leave the given starting point, which is what this routine does.
    #   Too, the same logic can apply at the ending point. that is, find when they stop coming together after they are near enough to the ending point.
    #   In both cases, we could be confused by two points being randomly very near each other while the gps unit bounces around the start/end point.
    #   Too, there's a problem of synchronizing the tracks. which points are to be paired up?

    stops   = stops or []

    routes  = []
    pi      = 0
    while   pi < len(t.points) :
        fp  = t.points[pi]
        fd  = frm.flat_distance_from(fp)
        if  fd  < cutoff_distance :
            # print "ffrom", pi, fd, fp
            pd      = fd
            while True :
                pi += 1
                if  pi >= len(t.points) :
                    break

                tp  = t.points[pi]
                td  = frm.flat_distance_from(tp)
                if  td >= cutoff_distance :                 # once we've moved far enough away from the "from" point, we'll start looking for the "to" point
                    # print "searching", pi, td, fp
                    while pi < len(t.points) :
                        tp  = t.points[pi]
                        td  = to.flat_distance_from(tp)
                        if  td  < cutoff_distance :
                            if  not _is_stop_in(stops, fp._track_idx, fp._point_idx, tp._track_idx, tp._point_idx) :
                                # print "found", pi, len(t.points), td, tp
                                pts = tz_gps.remove_redundant_points(t.points[fp._point_idx:tp._point_idx])
                                routes.append(a_route(fp, tp, distance = tz_gps.points_flat_distance(pts), duration = tp.when - fp.when, track = t, points = pts))
                            break
                        if  frm.flat_distance_from(tp) < cutoff_distance :
                            # print "back to from", pi, frm.flat_distance_from(tp), tp
                            break                           # if we wind back to the "from" point, start from there
                        pi += 1
                    break
                else :
                    if  fd  > td :
                        fd  = td
                        fp  = tp                            # we'll start from nearest point to "from" point
                    elif pd > td :
                        fd  = td
                        fp  = tp                            # we'll start from the point that subsequent points moved further away from "from"
                    pd      = td
                pass
            pass
        else :
            pi += 1
        pass

    return(routes)





def find_routes_in_tracks(tracks, frm = None, to = None, point_cutoff_distance = None) :
    """ Return an array of a_routes for the alternate routes in these tracks. """

    tracks      = tz_gps.fix_tracks_whens(tracks, remove_bad_tracks = True)
    tracks      = tz_gps.remove_untimed_points_from_tracks(tracks)              # note: redundant - we've already done so
    tracks      = tz_gps.remove_duplicate_tracks(tracks)
    tracks      = tz_gps.sorted_tracks_by_when(tracks)                          # note: redundant - done in a remove_ routine

    tracks      = tz_gps.set_tracks_points_to_known_track_point_idxes(tracks)


    if  False :
        tracks  = tz_gps.combine_near_points_in_tracks(tracks, point_cutoff_distance or POINT_CUTOFF_DISTANCE, False, False)
        tz_gps.write_gps_file("x.kmz", None, None, None, tz_gps.make_array_of_tracks(tracks))

        sys.exit(1)


    if  (frm == None) or (to == None) :
        (ppoints, stops)    = find_alternate_routes_in_tracks(tracks, point_cutoff_distance = point_cutoff_distance)
        ppoints.sort(lambda a, b : cmp(b[0].lat, a[0].lat) or cmp(a[0].lon, b[0].lon) or cmp(b[1].lat, a[1].lat) or cmp(a[1].lon, b[1].lon) or cmp(a[0].when, b[0].when) or cmp(a[1].when, b[1].when))
    else :
        ppoints     = [ [ frm, to ] ]
        stops       = []

    routes          = []
    for pa in ppoints :
        frm         = pa[0]
        to          = pa[1]
        ra          = []
        for t in tracks :
            ra     += find_from_to_route_in_track(t, frm, to, stops, cutoff_distance = point_cutoff_distance)
        routes.append(a_routes(frm, to, ra))

    return(routes)






def make_tracks_from_routes(routes) :
    """ Make tracks and waypoints for the given routes. """

    #
    #   Clean up any odd things that are in the data to protect code below
    #
    for ra in routes :
        ra.routes   = [ r  for r  in ra.routes if r.track and r.points and r.duration and r.distance ]
    routes          = [ ra for ra in    routes if ra.routes ]


    waypoints   = []
    otracks     = []

    if  routes  :

        set_routes_mx_fns(routes)

        lr          = count_routes_tracks_with_points(routes)

        ri          = 0
        for ra in routes :


            ldis    = 10000000000.0
            hdis    = 0.0
            ldur    = 10000000000.0
            hdur    = 0.0
            for r in ra.routes :
                ldis    = min(ldis, r.distance)
                hdis    = max(hdis, r.distance)
                ldur    = min(ldur, r.duration)
                hdur    = max(hdur, r.duration)


            ti      = 0
            for r in ra.routes :
                ti     += 1
                t       = tz_gps.a_track(id_num         = len(otracks),
                                         when           = r.points[0].when,
                                         name           = ("%u:%u" % ( ri + 1, ti ) ),
                                         description    = (   ""
                                                            + "<br>\nDuration: "
                                                            + tz_parse_time.hmmss_str(r.duration)
                                                            + ( "<br>\nDistance: %.1f" % (r.distance * latlon.milesPerNauticalMile) )
                                                            + "<br>\n"
                                                            + "From: "
                                                            + str(r.frm)
                                                            + "<br>\nTo:   "
                                                            + str(r.to)
                                                            + "<br>\n"
                                                          ),
                                         file_name      = r.track.file_name, points = r.points
                                        )
                ( red, green, blue )    = tz_gps.nice_track_color(lr, ri)           # note: all tracks in a particular route (each start/end point pair) are colored the same
                t.set_color(red, green, blue)
                if  hdis == ldis :
                    hdis       += 0.000000001
                if  hdur == ldur :
                    hdur       += 0.000000001
                t.set_opacity(((((hdis - float(r.distance)) / (hdis - ldis)) + ((hdur - float(r.duration)) / (hdur - ldur))) * 25.0) + 25.0)        # run the opacity from 25% to %75 (google's default is 50%)

                otracks.append(t)

            disdurs             = "Duration: %s - %s<br>\nDistance: %.1f - %.1f<br>\n" % (
                                                                                            tz_parse_time.hmmss_str(ldur), tz_parse_time.hmmss_str(hdur),
                                                                                            ldis * latlon.milesPerNauticalMile,
                                                                                            hdis * latlon.milesPerNauticalMile,
                                                                                         )

            p                   = ra.frm
            p.when              = None
            p                   = tz_gps.a_point(lat = p.lat, lon = p.lon, altitude = p.altitude, description = "Route: %u start.<br>\n" % ( ri + 1 ) + disdurs + "From: " + str(p) + "<br>\n")
            p.when              = None
            p.icon_image_url    = "http://maps.google.com/mapfiles/dd-start.png"                # !!!! too bad they aren't colored the same way as the routes
            waypoints.append(p)

            p                   = ra.to
            p.when              = None
            p                   = tz_gps.a_point(lat = p.lat, lon = p.lon, altitude = p.altitude, description = "Route: %u end.<br>\n"   % ( ri + 1 ) + disdurs + "To:   " + str(p) + "<br>\n")
            p.when              = None
            p.icon_image_url    = "http://maps.google.com/mapfiles/dd-end.png"
            waypoints.append(p)

            ri += 1
        pass

    return(waypoints, otracks)



def find_in_labels(labels, s) :
    """ Find the given name in the labels dictionary, returning the label a_circle() object. """
    s       = s.lower()
    if  s  in labels :
        return(labels[s])

    bl      = difflib.get_close_matches(s, labels.keys(), 1)
    if  len(bl) :
        return(labels[bl[0]])

    return(None)



help_str        = """
%s  (options) gps_file(s)...

Options:

    --from          lat,lon_or_label_name           Set the 'from' point.
    --to            lat,lon_or_label_name           Set the 'to'   point.

    --output        .gpx/.kml/.kmz/.nmea_file_name  Output routes to the given file name.

    --quiet                                         Do not output information to console.

    --point_cutoff_distance     nautical_miles
    --pd                        nautical_miles      Set the radius around the from/to points.   (default: %s nautical miles)

    --label_near    latlon NMI  label               Know this named label for a point.

Print information about routes recorded between the '--from' and '--to' points (spec'ed in lat,lon or close-matched label names).

"""


if  __name__ == '__main__' :
    import  glob
    import  os

    import  TZCommandLineAtFile


    program_name    = os.path.split(sys.argv.pop(0))[1]
    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    flat        = flon  = flbl  = tlan  = tlon  = tlbl  = None
    pnt_co_dist = POINT_CUTOFF_DISTANCE
    ofile_name  = None
    quiet       = False
    labels      = {}


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--help", "-h", "-?", "/h", "/H", "/?" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        print help_str % ( program_name,
                           ("%f" % pnt_co_dist).rstrip('0'),
                         )
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--label_near", "-l" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        s                       = sys.argv.pop(oi)
        ( lat, lon )            = latlon.parse_lat_lon(s)
        if  (lat == None) or (lon == None) :
            print "I cannot understand label_near of %s" % s
            sys.exit(103)
        p                       = tz_gps.a_circle(lat, lon, float(sys.argv.pop(oi)))
        p.name                  = sys.argv.pop(oi).strip()
        labels[p.name.lower()]  = p

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--from", "-f" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        lls             = sys.argv.pop(oi)
        ( flat, flon )  = latlon.parse_lat_lon(lls)
        if  flon   == None  :
            flbl    = find_in_labels(labels, lls)
            if  not flbl    :
                print "I (latlon.py) cannot understand %s!" % lls
                sys.exit(102)
            flat    = flbl.lat
            flon    = flbl.lon
        pass

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--to", "-t" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        lls             = sys.argv.pop(oi)
        ( tlat, tlon )  = latlon.parse_lat_lon(lls)
        if  tlon   == None :
            tlbl    = find_in_labels(labels, lls)
            if  not tlbl    :
                print "I (latlon.py) cannot understand %s!" % lls
                sys.exit(102)
            tlat    = tlbl.lat
            tlon    = tlbl.lon
        pass

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--output", "-o" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        ofile_name  = sys.argv.pop(oi)

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--quiet", "-q" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        quiet   = True

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--point_cutoff_distance", "--point-cutoff-distance", "--pointcutoffdistance", "--pd", "--pcd", "--pcod", "-d" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        pnt_co_dist = float(sys.argv.pop(oi))


    if  not len(sys.argv) :
        print "Tell me GPS track file(s) to analyze!"
        sys.exit(104)


    fnames  = {}
    while len(sys.argv) :
        fn  = sys.argv.pop(0)
        fns = tzlib.make_dictionary(glob.glob(fn))
        if  not fns :
            print "Cannot find file(s):", fn
            sys.exit(105)
        fnames.update(fns)


    fnames  = fnames.keys()
    fnames.sort()

    fpnt        = None
    if  flon   != None :
        fpnt    = tz_gps.a_point(lat = flat, lon = flon)
    tpnt        = None
    if  tlon   != None :
        tpnt    = tz_gps.a_point(lat = tlat, lon = tlon)
    tracks      = []
    for fn in fnames :
        trks    = tz_gps.tracks_from_file(fn)
        if  not trks :
            print "Cannot read GPS track from %s!" % fn
            sys.exit(106)
        if  (fpnt  != None) or (tpnt != None) :
            trks    = [ t for t in trks if (True if fpnt is None else t.can_have_point(fpnt)) and (True if tpnt is None else t.can_have_point(tpnt)) ]
        if  not len(trks) :
            # print "@@@@ No tracks with from/to points in", fn
            pass
        tracks += trks

    frm         = None
    to          = None
    if  (flon != None) or (tlon != None) :
        if  flon == None :
            if  not tracks or not tracks[0].points :
                print "Tell me --from where to check a route!"
                sys.exit(107)
            flat    = tracks[0].points[0].lat
            flon    = tracks[0].points[0].lon

        if  tlon == None :
            if  not tracks or not tracks[0].points :
                print "Tell me --to where to check a route!"
                sys.exit(107)
            tlat    = tracks[0].points[-1].lat
            tlon    = tracks[0].points[-1].lon

        frm         = tz_gps.a_point(lat = flat, lon = flon)
        to          = tz_gps.a_point(lat = tlat, lon = tlon)


    routes          = find_routes_in_tracks(tracks, frm, to, point_cutoff_distance = pnt_co_dist)

    if  not quiet   :
        set_routes_mx_fns(routes)

        ri          = 0
        for ra in routes :
            bi      = 0
            ti      = 0

            print
            print "Route:", ri + 1
            print "From: ", ra.frm, (flbl and flbl.name) or ""
            print "To:   ", ra.to,  (tlbl and tlbl.name) or ""

            for r in ra.routes :
                if  r.track and r.points :
                    ti += 1
                    bi  = 1
                    print "Route %u:%-3u %s" % ( ri + 1, ti, str(r) )
                pass

            ri += bi
        pass


    if  ofile_name  :
        ( waypoints, otracks )  = make_tracks_from_routes(routes)
        tz_gps.write_gps_file(ofile_name, "fastway.py", "fastway", waypoints, otracks)

    pass

#
#
#
# eof
