Added filter_far_from filter in ais.common extract tool
authorJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Wed, 19 Jan 2011 18:11:21 +0000 (18:11 +0000)
committerJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Wed, 19 Jan 2011 18:11:21 +0000 (18:11 +0000)
bin/common.py
bin/ntools.py

index a8d9f46b036599b3e997bc16c5dc252951a18acd..f747813a70ccb3275b603845a55d31ccfb12d20d 100755 (executable)
@@ -13,7 +13,7 @@ import csv
 from ais.ntools import *
 from ais.db import *
 from ais.area import load_area_from_kml_polygon
-from ais.earth3d import dist3_latlong_ais
+from ais.earth3d import dist3_latlong_ais, dist3_xyz, latlon_to_xyz_deg, latlon_to_xyz_ais
 
 __all__ = [
     'DB_STARTDATE', 'DBPATH',
@@ -1756,6 +1756,13 @@ def filter_area(nmea, area):
         return False
     return True
 
+def filter_far_from(nmea, miles, lat, lon):
+    '''
+    Returns true if position is farther than miles from (lat, lon)
+    '''
+    return dist3_xyz(latlon_to_xyz_deg(lat, lon), latlon_to_xyz_ais(nmea.latitude, nmea.longitude)) >= miles
+
+
 def filter_knownposition(nmea):
     """
     Returns false if position is not fully known
@@ -1857,6 +1864,9 @@ def main():
     parser.add_option('--filter-area',
         action='store', type='str', dest='area_file', metavar="FILE.KML",
         help="only process a specific area as defined in a kml polygon file.")
+    parser.add_option('--filter-farfrom',
+        action='store', dest='far_from', nargs=3, metavar='MILES LAT LONG',
+        help="only show ships farther than MILES miles from LAT,LONG")
 
     parser.add_option('--filter-destination',
         action='store', type='str', dest='filter_destination', metavar="DESTINATION",
@@ -1954,6 +1964,7 @@ def main():
     if options.sdt_start or options.granularity is not None or options.max_count:
         # time period is enabled
         if options.sdt_start:
+            # remove non digit characters
             options.sdt_start = "".join([ c for c in options.sdt_start if c.isdigit()])
             if len(options.sdt_start)==14:
                 dt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
@@ -1963,7 +1974,7 @@ def main():
                 print >> sys.stderr, "Invalid format for --start option"
                 sys.exit(1)
         else:
-            dt_start = dt_end - timedelta(1)
+            dt_start = dt_end - timedelta(1) # One day
         if options.granularity is None:
             options.granularity = 600
     else:
@@ -1990,6 +2001,16 @@ def main():
         area = load_area_from_kml_polygon(options.area_file)
         filters.append(lambda nmea: filter_area(nmea, area))
     
+    if options.far_from:
+        miles = float(options.far_from[0])
+        try:
+            lat = clean_latitude(unicode(options.far_from[1], 'utf-8'))
+            lon = clean_longitude(unicode(options.far_from[2], 'utf-8'))
+        except LatLonFormatError as err:
+            print >> sys.stderr, err.args
+            sys.exit(1)
+        filters.append(lambda nmea: filter_far_from(nmea, miles, lat, lon))
+    
     if options.type_list:
         def filter_type(nmea):
             return nmea.type in options.type_list
index 1a672edec6f150e7888b07855ef641a4e68fde4d..c60fc8dc48b4473b160ca4bc2fae332fbbb97798 100644 (file)
@@ -18,6 +18,9 @@ __all__ = [
     'alarm',
     'str_split_column_ipv6',
     'formataddr',
+    'LatLonFormatError',
+    'clean_latitude',
+    'clean_longitude',
     ]
 
 IPV4_IN_IPV6_PREFIX = '::ffff:'
@@ -131,6 +134,128 @@ def formataddr(addr):
         return addr
 
 
+
+
+class LatLonFormatError(ValueError):
+    pass
+
+
+def clean_latitude(data):
+    '''
+    Convert a string latitude into a float.
+    Raises LatLonFormatError on error.
+    '''
+    data = data.replace(u"''", u'"') # common mistake
+    data = data.replace(u' ', u'') # remove spaces
+    sides = u'SN'
+    if not data:
+        return None
+    tmp, side = data[:-1], data[-1]
+    if side == sides[0]:
+        side = -1
+    elif side == sides[1]:
+        side = 1
+    else:
+        raise LatLonFormatError(u'Last character must be either %s or %s.', sides[0], sides[1])
+    spl = tmp.split(u'°')
+    if len(spl) == 1:
+        raise LatLonFormatError(u'You need to use the ° character.')
+    d, tmp = spl
+    try:
+        d = float(d)
+    except ValueError:
+        raise LatLonFormatError(u'Degrees must be an number. It\'s %s.', d)
+    spl = tmp.split(u"'", 1)
+    if len(spl) == 1:
+        # no ' sign: ok only if there is nothing but the side after °
+        # we don't accept seconds if there is no minutes:
+        # It might be an entry mistake
+        tmp = spl[0]
+        if len(tmp) == 0:
+            m = s = 0
+        else:
+            raise LatLonFormatError(u'You must use the \' character between ° and %s.', data[-1])
+    else:
+        m, tmp = spl
+        try:
+            m = float(m)
+        except ValueError:
+            raise LatLonFormatError(u'Minutes must be an number. It\'s %s.', m)
+        if len(tmp) == 0:
+            s = 0
+        else:
+            if tmp[-1] != '"':
+                raise LatLonFormatError(u'You must use the " character between seconds and %s.', data[-1])
+            s = tmp[:-1]
+            try:
+                s = float(s)
+            except ValueError:
+                raise LatLonFormatError(u'Seconds must be an number. It\'s %s.', s)
+    data = side * ( d + m / 60 + s / 3600)
+
+    if data < -90 or data > 90:
+        raise LatLonFormatError(u'%s in not in -90..90 range', data)
+    return data
+
+
+
+def clean_longitude(data):
+    '''
+    Convert a string latitude into a float.
+    Raises LatLonFormatError on error.
+    '''
+    data = data.replace(u"''", u'"') # common mistake
+    data = data.replace(u' ', u'') # remove spaces
+    sides = u'WE'
+    if not data:
+        return None
+    tmp, side = data[:-1], data[-1]
+    if side == sides[0]:
+        side = -1
+    elif side == sides[1]:
+        side = 1
+    else:
+        raise LatLonFormatError(u'Last character must be either %s or %s.', sides[0], sides[1])
+    spl = tmp.split(u'°')
+    if len(spl) == 1:
+        raise LatLonFormatError(u'You need to use the ° character.')
+    d, tmp = spl
+    try:
+        d = float(d)
+    except ValueError:
+        raise LatLonFormatError(u'Degrees must be an number. It\'s %s.', d)
+    spl = tmp.split(u"'", 1)
+    if len(spl) == 1:
+        # no ' sign: ok only if there is nothing but the side after °
+        # we don't accept seconds if there is no minutes:
+        # It might be an entry mistake
+        tmp = spl[0]
+        if len(tmp) == 0:
+            m = s = 0
+        else:
+            raise LatLonFormatError(u'You must use the \' character between ° and %s.', data[-1])
+    else:
+        m, tmp = spl
+        try:
+            m = float(m)
+        except ValueError:
+            raise LatLonFormatError(u'Minutes must be an number. It\'s %s.', m)
+        if len(tmp) == 0:
+            s = 0
+        else:
+            if tmp[-1] != '"':
+                raise LatLonFormatError(u'You must use the " character between seconds and %s.', data[-1])
+            s = tmp[:-1]
+            try:
+                s = float(s)
+            except ValueError:
+                raise LatLonFormatError(u'Seconds must be an number. It\'s %s.', s)
+    data = side * ( d + m / 60 + s / 3600)
+
+    if data < -180 or data > 180:
+        raise LatLonFormatError(u'%s in not in -180..180 range', data)
+    return data
+
 #if __name__ == '__main__':
 #    for test in ('12:34:56:78',
 #                 ':12::56:',