Added filters --filter-sog-le --filter-farfrom --filter-closeto
[ais.git] / bin / show_targets_ships.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from __future__ import division
5 import sys
6 import logging
7 import zipfile
8 from StringIO import StringIO # TODO use python 2.6 io.BufferedWrite(sys.stdout, )
9 from datetime import datetime, timedelta, time
10 import copy
11
12 from ais.common import *
13 from ais.area import load_area_from_kml_polygon
14 from ais.ntools import datetime_to_timestamp, xml_escape
15
16 from ais.djais.settings import AIS_BASE_URL
17
18 __all__ = [ 'format_fleet', 'format_boat_intime', 'format_boat_track', 'STYLE', 'KML_DISPLAYOPT_NONAMES', 'KML_DISPLAYOPT_HISTORICAL', 'KML_DISPLAYOPT_SOURCES', 'kml_to_kmz' ]
19
20
21 KML_DISPLAYOPT_NONAMES = 1 # don't print ship name
22 KML_DISPLAYOPT_HISTORICAL = 2 # never show ship track as lost
23 KML_DISPLAYOPT_SOURCES = 4 # display sources
24
25
26 LOST_PERIOD = timedelta(1)
27
28 KML_HEADER = u'''\
29 <?xml version="1.0" encoding="UTF-8"?>
30 <kml xmlns="http://www.opengis.net/kml/2.2"
31     xmlns:gx="http://www.google.com/kml/ext/2.2">
32 <Document>
33 '''
34
35 KML_FOOTER = u'''\
36 </Document>
37 </kml>
38 '''
39
40    
41
42 class Style:
43     """
44     KML style for ship presentation.
45     It contains a list of png icons that were used.
46     This is a virtual class.
47     """
48     def __init__(self):
49         self.label_size = 0.7
50         self.icon_size = 0.5 # 0.2
51         self.used_icons = set()
52
53     def _format_style(self, stylename, icon, heading=None, color=None):
54         """
55         color format is google styled: aabbggrr
56         example ffea00ff for purple
57         """
58         result = u''
59         if heading is not None:
60             stylename += '-' + str(heading)
61         result += '<Style id="%s">\n' % stylename
62         result += '  <LabelStyle>\n'
63         result += '    <scale>%f</scale>\n' % self.label_size
64         result += '  </LabelStyle>\n'
65         result += '  <IconStyle>\n'
66         result += '    <Icon>\n'
67         result += '      <href>%s</href>\n' % icon
68         result += '    </Icon>\n'
69         if heading is not None:
70             result += '    <heading>%d</heading>\n' % heading
71         if color is not None:
72             result += '    <color>%s</color>\n' % color
73         result += '    <scale>%f</scale>\n' % self.icon_size
74         result += '    <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>\n'
75         result += '  </IconStyle>\n'
76         result += '</Style>\n'
77         self.used_icons.add(icon)
78         return result
79     
80     def make_header(self):
81         raise NotImplementedError # abstract class
82     
83     def get_style_name(self, nmea, is_lost):
84         '''
85         Returns the name of the style based on nmea data
86         and whether the ship was seen recently or not.
87         '''
88         raise NotImplementedError # abstract class
89
90
91 class FishersStyle(Style):
92     def make_header(self):
93         white = None
94         green = 'ff86fd5f' # '5f-fd-86'
95         yellow = 'ff86eeff' #'ff-ee-86'
96         red = 'ff5865fc' #'fc-65-58'
97         result = u''
98         result += self._format_style('landstation', 'capital_small.png')
99
100         result += self._format_style('base-stop', 'boat-stop.png', color=white)
101         result += self._format_style('fisher-stop', 'boat-stop.png', color=red)
102         result += self._format_style('tug-stop', 'boat-stop.png', color=green)
103         result += self._format_style('auth-stop', 'boat-stop.png', color=yellow)
104         
105         for heading in [ None ] + range(0, 360, 10):
106             result += self._format_style('base', 'boat.png', color=white, heading=heading)
107             result += self._format_style('fisher', 'boat.png', color=red, heading=heading)
108             result += self._format_style('tug', 'boat.png', color=green, heading=heading)
109             result += self._format_style('auth', 'boat.png', color=yellow, heading=heading)
110             result += self._format_style('base-lost', 'boat-invis.png', color=white, heading=heading)
111             result += self._format_style('fisher-lost', 'boat-invis.png', color=red, heading=heading)
112             result += self._format_style('tug-lost', 'boat-invis.png', color=green, heading=heading)
113             result += self._format_style('auth-lost', 'boat-invis.png', color=yellow, heading=heading)
114         
115         return result
116
117     def get_style_name(self, nmea, is_lost):
118         '''
119         Returns the name of the style based on nmea data
120         and whether the ship was seen recently or not.
121         '''
122         if nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000'):
123             return 'landstation'
124         
125         if nmea.type == 30: # Fisher ship
126             stylename = 'fisher'
127         elif nmea.type in (31, 32, 52): # Towing or Tug
128             stylename = 'tug'
129         elif nmea.type in (35, 53, 55): # Authority
130             stylename = 'auth'
131         else:
132             stylename = 'base'
133
134         if (nmea.status in (1, 5, 6) and (nmea.sog == AIS_SOG_NOT_AVAILABLE or nmea.sog<0.5*AIS_SOG_SCALE)) \
135            or nmea.sog<1*AIS_SOG_SCALE:
136             stylename += '-stop'
137         else:
138             if is_lost:
139                 stylename += '-lost'
140             
141             if nmea.cog != AIS_COG_NOT_AVAILABLE:
142                 course = int(nmea.cog/10.) # ais format correction
143                 course = (course+5)//10*10 % 360 # go to neareast 10°
144                 stylename += '-%d' % course
145             elif nmea.heading != AIS_NO_HEADING:
146                 course = (nmea.heading+5)//10*10 % 360 # go to neareast 10°
147                 stylename += '-%d' % course
148         return stylename
149
150
151 class PelagosStyle(Style):
152     def make_header(self):
153         white = None
154         green = 'ff86fd5f' # '5f-fd-86'
155         yellow = 'ff86eeff' #'ff-ee-86'
156         pink = 'ffff00ea' #'ea-00-ff'
157         red = 'ff5865fc' #'fc-65-58'
158
159         result = u''
160         result += self._format_style('landstation', 'capital_small.png')
161
162         result += self._format_style('base-stop', 'boat-stop.png', color=white)
163         result += self._format_style('cargo-stop', 'boat-stop.png', color=green)
164         result += self._format_style('tanker-stop', 'boat-stop.png', color=yellow)
165         result += self._format_style('hsc-stop', 'boat-stop.png', color=pink)
166         result += self._format_style('hazarda-stop', 'boat-stop.png', color=red)
167
168         for heading in [ None ] + range(0, 360, 10):
169             result += self._format_style('base', 'boat.png', color=white, heading=heading)
170             result += self._format_style('cargo', 'boat.png', color=green, heading=heading)
171             result += self._format_style('tanker', 'boat.png', color=yellow, heading=heading)
172             result += self._format_style('hsc', 'boat.png', color=pink, heading=heading)
173             result += self._format_style('hazarda', 'boat.png', color=red, heading=heading)
174
175             result += self._format_style('base-lost', 'boat-invis.png', color=white, heading=heading)
176             result += self._format_style('cargo-lost', 'boat-invis.png', color=green, heading=heading)
177             result += self._format_style('tanker-lost', 'boat-invis.png', color=yellow, heading=heading)
178             result += self._format_style('hsc-lost', 'boat-invis.png', color=pink, heading=heading)
179             result += self._format_style('hazarda-lost', 'boat-invis.png', color=red, heading=heading)
180         
181         return result
182
183     def get_style_name(self, nmea, is_lost):
184         '''
185         Returns the name of the style based on nmea data
186         and whether the ship was seen recently or not.
187         '''
188         if (nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000')):
189             return 'landstation'
190         
191         if nmea.type in (41, 61, 71, 81): # major hazard materials
192             stylename = 'hazarda'
193         elif nmea.type >= 70 and nmea.type <= 79:
194             stylename = 'cargo'
195         elif nmea.type >= 80 and nmea.type <= 89:
196             stylename = 'tanker'
197         elif nmea.type >= 40 and nmea.type <= 49:
198             stylename = 'hsc'
199         else:
200             stylename = 'base'
201
202         if (nmea.status in (1, 5, 6) and (nmea.sog == AIS_SOG_NOT_AVAILABLE or nmea.sog<0.5*AIS_SOG_SCALE)) \
203            or nmea.sog<1*AIS_SOG_SCALE:
204             stylename += '-stop'
205         else:
206             if is_lost:
207                 stylename += '-lost'
208             
209             if nmea.cog != AIS_COG_NOT_AVAILABLE:
210                 course = int(nmea.cog/10.) # ais format correction
211                 course = (course+5)//10*10 % 360 # go to neareast 10°
212                 stylename += '-%d' % course
213             elif nmea.heading != AIS_NO_HEADING:
214                 course = (nmea.heading+5)//10*10 % 360 # go to neareast 10°
215                 stylename += '-%d' % course
216         return stylename
217
218 STYLE = FishersStyle()
219
220
221 def format_boat_data(nmea, timeinfo=None, display_options=0):
222     '''
223     timeinfo: None to generate a GoogleEarth 4 file, with no timeing information
224               True to generate a GoogleEarth 5 file, with start time from nmea
225               datetime or timestamp instance to generate a GoogleEarth 5 file, with start time from nmea and this enddate
226     '''
227     mmsi = nmea.strmmsi
228     timestamp_1, status, rot, sog, latitude, longitude, cog, heading, source_1 = Nmea1.to_values(nmea)
229     timestamp_5, imo, name, callsign, type_, dim_bow, dim_stern, dim_port, dim_starboard, eta_M, eta_D, eta_h, eta_m, draught, destination, source_5 = Nmea5.to_values(nmea)
230
231     if latitude == AIS_LAT_NOT_AVAILABLE or longitude == AIS_LON_NOT_AVAILABLE:
232         return u''
233
234     result = u''
235
236     if timeinfo is not None and timeinfo != True:
237         if not isinstance(timeinfo, datetime):
238             timeinfo = datetime.utcfromtimestamp(timeinfo)
239             
240     result += u'<Placemark>\n'
241
242     if not (display_options & KML_DISPLAYOPT_NONAMES):
243         result += u'<name>' + xml_escape(nmea.get_title()) + u'</name>\n'
244
245     result += u'<description><![CDATA[\n'
246     if display_options & KML_DISPLAYOPT_NONAMES:
247         result += u'Vessel name: ' + xml_escape(nmea.get_name()) + u'<br>\n'
248         
249     dt_1 = datetime.utcfromtimestamp(timestamp_1)
250     if display_options & KML_DISPLAYOPT_HISTORICAL:
251         result += u'%s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
252         is_lost = None
253     else:
254         if timeinfo is None:
255             is_lost = dt_1 < datetime.utcnow()-LOST_PERIOD
256             if is_lost:
257                 result += u'Tack <b>lost</b> since %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
258             else:
259                 result += u'Last seen %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
260         else: # timeinfo is not None
261             if timeinfo == True:
262                 is_lost = None
263             else:
264                 is_lost = timeinfo > dt_1 + LOST_PERIOD
265
266     if not mmsi.isdigit():
267         result += u'NO MMSI<br>\n'
268         is_land_station = False
269     else:
270         result += u'MMSI: %s ' % mmsi
271         ref_mmsi = str(mmsi) # FIXME not needed
272         is_land_station = ref_mmsi.startswith('00') and not ref_mmsi.startswith('000')
273         if is_land_station:
274             ref_mmsi = ref_mmsi[2:]
275         result += u'('+COUNTRIES_MID.get(int(ref_mmsi[:3]), u'fake')+u')<br>\n'
276     if not is_land_station :
277         if imo:
278             #result += u'IMO<a href="http://www.xvas.it/SPECIAL/VTship.php?imo=%(imo)s&amp;mode=CK">%(imo)s</a><br>\n' % { 'imo': imo }
279             result += u'IMO: %s<br>\n' % imo
280         else:
281             result += u'no known IMO<br>\n'
282     callsign = nmea.get_callsign(default=None)
283     if callsign is not None:
284         result += u'Callsign: %s<br>\n' % xml_escape(callsign)
285     if type_:
286         result += u'Type: %s<br>\n' % SHIP_TYPES.get(type_, 'unknown')
287     if status != AIS_STATUS_NOT_AVAILABLE:
288         result += u'Status: %s<br>\n' % STATUS_CODES.get(status, 'unknown')
289     if cog != AIS_COG_NOT_AVAILABLE:
290         result += u'Course: %.01f°<br>\n' % (cog/10.)
291     if heading != AIS_NO_HEADING:
292         result += u'Heading: %d°<br>\n' % heading
293     if sog != AIS_SOG_NOT_AVAILABLE:
294         if sog != AIS_SOG_FAST_MOVER:
295             result += u'Speed: %.01f kts<br>\n' % (sog/AIS_SOG_SCALE)
296         else:
297             result += u'Speed: more that than 102.2 kts<br>\n'
298     length = nmea.get_length()
299     width = nmea.get_width()
300     if length or width or draught:
301         result += u'Size: %dx%d' % (length, width)
302         if draught:
303             result += u'/%.01f' % (draught/10.)
304         result += u'm<br>\n'
305     destination = nmea.get_destination(default=None)
306     if destination:
307         result += u'Destination: %s<br>\n' % xml_escape(destination)
308     eta = nmea.get_eta_str(default=None)
309     if eta is not None:
310         result += u'ETA: %s<br>\n' % eta
311
312     if (display_options & KML_DISPLAYOPT_SOURCES) and (source_1 or source_5):
313         result += u'Source: '
314         if source_1:
315             result += Nmea.format_source(source_1)
316         if source_5 and source_1 != source_5:
317             result += u', '+ Nmea.format_source(source_5)
318         result += u'<br>\n'
319     result += u'<a href="' + AIS_BASE_URL + u'/vessel/%(mmsi)s/">More...</a>' \
320               % {'mmsi': mmsi}
321     result += u']]>\n'
322     result += u'</description>\n'
323
324     result += u'<styleUrl>#%s</styleUrl>\n' \
325               % STYLE.get_style_name(nmea, is_lost)
326
327     result += u'<Point>\n'
328     result += u'<coordinates>%s,%s</coordinates>' \
329         % (longitude/AIS_LATLON_SCALE, latitude/AIS_LATLON_SCALE)
330     result += u'</Point>\n'
331
332     if timeinfo is not None:
333         #result += u'<TimeStamp><when>%s</when></TimeStamp>\n' \
334         #          % (dt_1.strftime('%Y-%m-%dT%H:%M:%SZ'))
335         result += u'<gx:TimeSpan><begin>%s</begin>' \
336                   % dt_1.strftime('%Y-%m-%dT%H:%M:%SZ')
337         if timeinfo != True:
338             result += u'<end>%s</end>' \
339                       % timeinfo.strftime('%Y-%m-%dT%H:%M:%SZ')
340         result += u'</gx:TimeSpan>\n'
341     result += u'</Placemark>\n'
342     return result
343
344
345
346
347 def format_fleet(mmsi_iterator, document_name=None):
348     result = u''
349     result += KML_HEADER
350
351     if document_name is None:
352         document_name = 'AIS database'
353     result += u'<name>%s</name>\n' % document_name
354     
355     result += STYLE.make_header()
356
357     long_ago = datetime_to_timestamp(datetime.utcnow() - timedelta(90))
358
359     for mmsi in mmsi_iterator:
360         nmea = Nmea.new_from_lastinfo(mmsi)
361         if nmea.get_last_timestamp() < long_ago:
362             continue
363         result += format_boat_data(nmea, display_options=KML_DISPLAYOPT_SOURCES)
364     result += KML_FOOTER
365     return result
366
367
368 def format_boat_intime_section(nmea_iterator, kml_displayopt=0):
369     result = u''
370     last_nmea = None
371     for nmea in nmea_iterator:
372         if last_nmea is None:
373             timeinfo = True
374         else:
375             timeinfo = datetime.utcfromtimestamp(last_nmea.timestamp_1)
376
377         result += format_boat_data(nmea, timeinfo,
378                                    kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
379         # make a copy because nmea will be patched with new data:
380         last_nmea = copy.copy(nmea)
381     if not result:
382         result += u'<description>Vessel not found</description>'
383     return result
384
385
386 def format_boat_intime(nmea_iterator):
387     result = u''
388     result += KML_HEADER
389     result += STYLE.make_header()
390     result += format_boat_intime_section(nmea_iterator)
391     result += KML_FOOTER
392     return result
393
394
395 def format_boat_track_section(nmea_iterator, name=u''):
396     strcoordinates = '<Placemark>\n<LineString>\n<coordinates>\n'
397     segment_length = 0
398     for nmea in nmea_iterator:
399         if name == u'':
400             name = nmea.get_title()
401         if nmea.longitude != AIS_LON_NOT_AVAILABLE and nmea.latitude != AIS_LAT_NOT_AVAILABLE:
402             if segment_length > 65000:
403                 logging.debug('Line is too long. Spliting.')
404                 strcoordinates += ' %.8f,%.8f' \
405                                   % (nmea.longitude/AIS_LATLON_SCALE,
406                                      nmea.latitude/AIS_LATLON_SCALE)
407                 strcoordinates += '</coordinates>\n</LineString>\n</Placemark>\n<Placemark>\n<LineString>\n<coordinates>\n'
408                 segment_length = 0
409             else:
410                 segment_length += 1
411             strcoordinates += ' %.8f,%.8f' \
412                               % (nmea.longitude/AIS_LATLON_SCALE,
413                                  nmea.latitude/AIS_LATLON_SCALE)
414     strcoordinates += '</coordinates>\n</LineString></Placemark>\n'
415
416     result = u''
417     result += u'<name>%s track</name>\n' % name
418     if len(strcoordinates)>39+2*(1+12+1+11)+42+1:
419         result += unicode(strcoordinates)
420     else:
421         result += u'<description>No data available</description>\n'
422     return result
423
424
425 def format_boat_track(nmea_iterator):
426     result = u''
427     result += KML_HEADER
428     #result += STYLE.make_header()
429     result += format_boat_track_section(nmea_iterator)
430     result += KML_FOOTER
431     return result
432
433
434
435 def kml_to_kmz(inputstr):
436     if isinstance(inputstr, unicode):
437         inputstr = inputstr.encode('utf-8')
438     output = StringIO()
439     kmz = zipfile.ZipFile(output, 'w')
440     kmz.writestr('doc.kml', inputstr)
441     for iconname in STYLE.used_icons:
442         kmz.write('/usr/lib/ais/kmz_icons/'+iconname, iconname)
443     kmz.close()
444     return output.getvalue()
445     
446
447 def main():
448     global DBPATH, STYLE
449     from optparse import OptionParser, OptionGroup
450
451     parser = OptionParser(usage='%prog [options] { mmsi | @fleetname | #fleetid }+ | all')
452
453     parser.add_option('-d', '--debug',
454         action='store_true', dest='debug', default=False,
455         help="debug mode")
456
457     parser.add_option('-e', '--end',
458         action='store', dest='sdt_end', metavar="'YYYYMMDD HHMMSS'",
459         help='End data processing on that GMT date time.'
460              'Default is now.'
461              'If a date is provided without time, time defaults to 235959.')
462     parser.add_option('-s', '--start',
463         action='store', dest='sdt_start', metavar="'YYYYMMDD HHMMSS'",
464         help='Start data processing on that date.'
465              'Using that option enables multiple output of the same boat.'
466              'Disabled by default.'
467              'If a date is provided without time, time default to 000000.'
468              'If other options enable multiple output, default to 1 day before'
469              ' --end date/time.')
470     parser.add_option('-g', '--granularity',
471         action='store', type='int', dest='granularity', metavar='SECONDS',
472         help='Dump only one position every granularity seconds.'
473              'Using that option enables multiple output of the same boat.'
474              'If other options enable multiple output, defaults to 600'
475              ' (10 minutes)')
476     parser.add_option('--max',
477         action='store', type='int', dest='max_count', metavar='NUMBER',
478         help='Dump a maximum of NUMBER positions every granularity seconds.'
479              'Using that option enables multiple output of the same boat.')
480
481     parser.add_option('--filter-knownposition',
482         action='store_true', dest='filter_knownposition', default=False,
483         help="eliminate unknown positions from results.")
484
485     parser.add_option('--filter-speedcheck',
486         action='store', type='int', dest='speedcheck', default=200, metavar='KNOTS',
487         help='Eliminate erroneaous positions from results,' 
488              ' based on impossible speed.')
489
490     parser.add_option('--filter-type',
491         action='append', type='int', dest='type_list', metavar="TYPE",
492         help="process a specific ship type.")
493     parser.add_option('--help-types',
494         action='store_true', dest='help_types', default=False,
495         help="display list of available types")
496
497     parser.add_option('--filter-area',
498         action='store', type='str', dest='area_file', metavar="FILE.KML",
499         help="only process a specific area as defined in a kml polygon file.")
500     parser.add_option('--filter-farfrom',
501         action='store', dest='far_from', nargs=3, metavar='LAT LONG MILES',
502         help="only show ships farther than MILES miles from LAT,LONG")
503     parser.add_option('--filter-closeto',
504         action='store', dest='close_to', nargs=3, metavar='LAT LONG MILES',
505         help="only show ships closer than MILES miles from LAT,LONG")
506     parser.add_option('--filter-sog-le',
507         action='store', dest='sog_le', metavar='KNOTS',
508         help='only show ships when speed over ground is less or equal to KNOTS.')
509
510     #
511     parser.add_option('--format',
512         choices=('positions', 'track', 'animation'), dest='format', default='positions',
513         help="select output format: positions(*) or track or animation")
514
515     parser.add_option('--kml',
516         action='store_true', dest='output_kml', default=False,
517         help="Output a kml file. Default is output of a kmz file with icons")
518     parser.add_option('--inner-kml',
519         action='store_true', dest='output_innerkml', default=False,
520         help='Output a kml fragment file without the <Document> wrappers.\n'
521              'File should be reproccessed to be usefull. That option implies --kml')
522
523     parser.add_option('--style',
524         choices=('fishers', 'pelagos'), dest='style', default='fishers',
525         help='select one of the predefined style for display: '
526              'fishers(*) or pelagos')
527
528     parser.add_option('--icon-size',
529         action='store', type='float', dest='icon_size', metavar='SCALE', default=0.5,
530         help='Set icons size. Default = %default')
531     
532     parser.add_option('--no-names',
533         action='store_const', const=KML_DISPLAYOPT_NONAMES,
534         dest='kml_displayopt_noname', default=0,
535         help="don't show ship names")
536
537     parser.add_option('--show-sources',
538         action='store_const', const=KML_DISPLAYOPT_SOURCES,
539         dest='kml_displayopt_sources', default=0,
540         help="show information source")
541
542     #
543
544     expert_group = OptionGroup(parser, "Expert Options",
545         "You normaly don't need any of these")
546
547     expert_group.add_option('--db',
548         action='store', dest='db', default=DBPATH,
549         help="path to filesystem database. Default=%default")
550     parser.add_option_group(expert_group)
551
552     options, args = parser.parse_args()
553
554     
555     if options.help_types:
556         keys = SHIP_TYPES.keys()
557         keys.sort()
558         for k in keys:
559             print k, SHIP_TYPES[k]
560         sys.exit(0)
561
562     DBPATH = options.db
563
564     if options.debug:
565         loglevel = logging.DEBUG
566     else:
567         loglevel = logging.INFO
568     logging.basicConfig(level=loglevel, format='%(asctime)s %(levelname)s %(message)s')
569
570     #
571     # Ships selections
572     #
573
574     if len(args)==0:
575         print >> sys.stderr, "No ship to process"
576         sys.exit(1)
577
578     target_mmsi_iterator = []
579     all_targets = False
580     for arg in args:
581         if arg == 'all':
582             all_targets = True
583         elif arg.startswith('@'):
584             target_mmsi_iterator += load_fleet_to_uset(fleetname_to_fleetid(arg[1:]))
585         elif arg.startswith('^'):
586             target_mmsi_iterator += load_fleet_to_uset(int(arg[1:]))
587         else:
588             target_mmsi_iterator.append(arg)
589     if all_targets:
590         if target_mmsi_iterator:
591             logging.warning('Selecting all ships, ignoring other arguments')
592         target_mmsi_iterator = all_mmsi_generator()
593
594     #
595     # Dates selections
596     #
597
598     if options.sdt_end:
599         # remove non digit characters
600         options.sdt_end = "".join([ c for c in options.sdt_end if c.isdigit()])
601         if len(options.sdt_end)==14:
602             dt_end = datetime.strptime(options.sdt_end, '%Y%m%d%H%M%S')
603         elif len(options.sdt_end)==8:
604             dt_end = datetime.strptime(options.sdt_end, '%Y%m%d')
605             dt_end = datetime.combine(dt_end.date(), time(23,59,59))
606         else:
607             print >> sys.stderr, "Invalid format for --end option"
608             sys.exit(1)
609     else:
610         dt_end = datetime.utcnow()
611     logging.debug('--end is %s', dt_end)
612
613     if options.sdt_start or options.granularity is not None or options.max_count or options.format in ('animation', 'track'):
614         # time period is enabled
615         if options.sdt_start:
616             options.sdt_start = "".join([ c for c in options.sdt_start if c.isdigit()])
617             if len(options.sdt_start)==14:
618                 dt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
619             elif len(options.sdt_start)==8:
620                 dt_start = datetime.strptime(options.sdt_start, '%Y%m%d')
621             else:
622                 print >> sys.stderr, "Invalid format for --start option"
623                 sys.exit(1)
624         else:
625             dt_start = dt_end - timedelta(1)
626         if options.granularity is None:
627             options.granularity = 600
628     else:
629         dt_start = None
630         options.max_count = 1
631         if options.granularity is None:
632             options.granularity = 600
633     logging.debug('--start is %s', dt_start)
634
635
636     #
637     # Filters
638     #
639
640     filters = []
641     
642     if options.filter_knownposition:
643         filters.append(filter_knownposition)
644
645     if options.speedcheck != 0:
646         maxmps = options.speedcheck / 3600. # from knots to NM per seconds
647         filters.append(lambda nmea: filter_speedcheck(nmea, maxmps))
648
649     if options.area_file:
650         area = load_area_from_kml_polygon(options.area_file)
651         filters.append(lambda nmea: filter_area(nmea, area))
652
653     if options.close_to:
654         try:
655             lat = clean_latitude(unicode(options.close_to[0], 'utf-8'))
656             lon = clean_longitude(unicode(options.close_to[1], 'utf-8'))
657         except LatLonFormatError as err:
658             print >> sys.stderr, err.args
659             sys.exit(1)
660         miles = float(options.close_to[2])
661         filters.append(lambda nmea: filter_close_to(nmea, lat, lon, miles))
662
663     if options.far_from:
664         try:
665             lat = clean_latitude(unicode(options.far_from[0], 'utf-8'))
666             lon = clean_longitude(unicode(options.far_from[1], 'utf-8'))
667         except LatLonFormatError as err:
668             print >> sys.stderr, err.args
669             sys.exit(1)
670         miles = float(options.far_from[2])
671         filters.append(lambda nmea: filter_far_from(nmea, lat, lon, miles))
672     
673     if options.sog_le:
674         filters.append(lambda nmea: filter_sog_le(nmea, float(options.sog_le)))
675
676     if options.type_list:
677         def filter_type(nmea):
678             #print nmea.type, repr(options.type_list), nmea.type in options.type_list
679             #print repr(nmea.get_dump_row())
680             return nmea.type in options.type_list
681         filters.append(filter_type)
682
683     #
684     # Display options
685     #
686
687     if options.style == 'pelagos':
688         STYLE = PelagosStyle()
689
690     kml_displayopt = options.kml_displayopt_noname | options.kml_displayopt_sources
691
692     STYLE.icon_size = options.icon_size
693
694     if options.output_innerkml:
695         options.output_kml = True
696     #
697     # Processing
698     #
699
700     if options.format == 'positions':
701         result = u''
702         if not options.output_innerkml:
703             result += KML_HEADER
704             result += STYLE.make_header()
705         for mmsi in target_mmsi_iterator:
706             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
707             for nmea in nmea_generator:
708                 result += format_boat_data(nmea, None, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
709         if not options.output_innerkml:
710             result += KML_FOOTER
711
712     elif options.format=='animation':
713         result = u''
714         if not options.output_innerkml:
715             result += KML_HEADER
716             result += STYLE.make_header()
717         for mmsi in target_mmsi_iterator:
718             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
719             result += '<Folder>\n'
720             result += format_boat_intime_section(nmea_generator, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
721             result += '</Folder>\n'
722         if not options.output_innerkml:
723             result += KML_FOOTER
724                
725     elif options.format=='track':
726         result = u''
727         if not options.output_innerkml:
728             result += KML_HEADER
729             # don't call STYLE.make_header since there is no icons
730         for mmsi in target_mmsi_iterator:
731             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
732             result += '<Folder>\n'
733             result += format_boat_track_section(nmea_generator)
734             result += '</Folder>\n'
735         if not options.output_innerkml:
736             result += KML_FOOTER
737
738     else:
739         print >> sys.stderr, 'Unknown output format'
740         sys.exit(1)
741         
742     result = result.encode('utf-8')
743
744     if not options.output_kml:
745         result = kml_to_kmz(result)
746
747     print result
748
749 if __name__ == '__main__':
750     main()