da1ba6f92f7324d51d9cbb32c2147444c7f151f3
[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
501     #
502     parser.add_option('--format',
503         choices=('positions', 'track', 'animation'), dest='format', default='positions',
504         help="select output format: positions(*) or track or animation")
505
506     parser.add_option('--kml',
507         action='store_true', dest='output_kml', default=False,
508         help="Output a kml file. Default is output of a kmz file with icons")
509     parser.add_option('--inner-kml',
510         action='store_true', dest='output_innerkml', default=False,
511         help='Output a kml fragment file without the <Document> wrappers.\n'
512              'File should be reproccessed to be usefull. That option implies --kml')
513
514     parser.add_option('--style',
515         choices=('fishers', 'pelagos'), dest='style', default='fishers',
516         help='select one of the predefined style for display: '
517              'fishers(*) or pelagos')
518
519     parser.add_option('--icon-size',
520         action='store', type='float', dest='icon_size', metavar='SCALE', default=0.5,
521         help='Set icons size. Default = %default')
522     
523     parser.add_option('--no-names',
524         action='store_const', const=KML_DISPLAYOPT_NONAMES,
525         dest='kml_displayopt_noname', default=0,
526         help="don't show ship names")
527
528     parser.add_option('--show-sources',
529         action='store_const', const=KML_DISPLAYOPT_SOURCES,
530         dest='kml_displayopt_sources', default=0,
531         help="show information source")
532
533     #
534
535     expert_group = OptionGroup(parser, "Expert Options",
536         "You normaly don't need any of these")
537
538     expert_group.add_option('--db',
539         action='store', dest='db', default=DBPATH,
540         help="path to filesystem database. Default=%default")
541     parser.add_option_group(expert_group)
542
543     options, args = parser.parse_args()
544
545     
546     if options.help_types:
547         keys = SHIP_TYPES.keys()
548         keys.sort()
549         for k in keys:
550             print k, SHIP_TYPES[k]
551         sys.exit(0)
552
553     DBPATH = options.db
554
555     if options.debug:
556         loglevel = logging.DEBUG
557     else:
558         loglevel = logging.INFO
559     logging.basicConfig(level=loglevel, format='%(asctime)s %(levelname)s %(message)s')
560
561     #
562     # Ships selections
563     #
564
565     if len(args)==0:
566         print >> sys.stderr, "No ship to process"
567         sys.exit(1)
568
569     target_mmsi_iterator = []
570     all_targets = False
571     for arg in args:
572         if arg == 'all':
573             all_targets = True
574         elif arg.startswith('@'):
575             target_mmsi_iterator += load_fleet_to_uset(fleetname_to_fleetid(arg[1:]))
576         elif arg.startswith('^'):
577             target_mmsi_iterator += load_fleet_to_uset(int(arg[1:]))
578         else:
579             target_mmsi_iterator.append(arg)
580     if all_targets:
581         if target_mmsi_iterator:
582             logging.warning('Selecting all ships, ignoring other arguments')
583         target_mmsi_iterator = all_mmsi_generator()
584
585     #
586     # Dates selections
587     #
588
589     if options.sdt_end:
590         # remove non digit characters
591         options.sdt_end = "".join([ c for c in options.sdt_end if c.isdigit()])
592         if len(options.sdt_end)==14:
593             dt_end = datetime.strptime(options.sdt_end, '%Y%m%d%H%M%S')
594         elif len(options.sdt_end)==8:
595             dt_end = datetime.strptime(options.sdt_end, '%Y%m%d')
596             dt_end = datetime.combine(dt_end.date(), time(23,59,59))
597         else:
598             print >> sys.stderr, "Invalid format for --end option"
599             sys.exit(1)
600     else:
601         dt_end = datetime.utcnow()
602     logging.debug('--end is %s', dt_end)
603
604     if options.sdt_start or options.granularity is not None or options.max_count or options.format in ('animation', 'track'):
605         # time period is enabled
606         if options.sdt_start:
607             options.sdt_start = "".join([ c for c in options.sdt_start if c.isdigit()])
608             if len(options.sdt_start)==14:
609                 dt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
610             elif len(options.sdt_start)==8:
611                 dt_start = datetime.strptime(options.sdt_start, '%Y%m%d')
612             else:
613                 print >> sys.stderr, "Invalid format for --start option"
614                 sys.exit(1)
615         else:
616             dt_start = dt_end - timedelta(1)
617         if options.granularity is None:
618             options.granularity = 600
619     else:
620         dt_start = None
621         options.max_count = 1
622         if options.granularity is None:
623             options.granularity = 600
624     logging.debug('--start is %s', dt_start)
625
626
627     #
628     # Filters
629     #
630
631     filters = []
632     
633     if options.filter_knownposition:
634         filters.append(filter_knownposition)
635
636     if options.speedcheck != 0:
637         maxmps = options.speedcheck / 3600. # from knots to NM per seconds
638         filters.append(lambda nmea: filter_speedcheck(nmea, maxmps))
639
640     if options.area_file:
641         area = load_area_from_kml_polygon(options.area_file)
642         filters.append(lambda nmea: filter_area(nmea, area))
643
644     if options.type_list:
645         def filter_type(nmea):
646             #print nmea.type, repr(options.type_list), nmea.type in options.type_list
647             #print repr(nmea.get_dump_row())
648             return nmea.type in options.type_list
649         filters.append(filter_type)
650
651     #
652     # Display options
653     #
654
655     if options.style == 'pelagos':
656         STYLE = PelagosStyle()
657
658     kml_displayopt = options.kml_displayopt_noname | options.kml_displayopt_sources
659
660     STYLE.icon_size = options.icon_size
661
662     if options.output_innerkml:
663         options.output_kml = True
664     #
665     # Processing
666     #
667
668     if options.format == 'positions':
669         result = u''
670         if not options.output_innerkml:
671             result += KML_HEADER
672             result += STYLE.make_header()
673         for mmsi in target_mmsi_iterator:
674             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
675             for nmea in nmea_generator:
676                 result += format_boat_data(nmea, None, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
677         if not options.output_innerkml:
678             result += KML_FOOTER
679
680     elif options.format=='animation':
681         result = u''
682         if not options.output_innerkml:
683             result += KML_HEADER
684             result += STYLE.make_header()
685         for mmsi in target_mmsi_iterator:
686             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
687             result += '<Folder>\n'
688             result += format_boat_intime_section(nmea_generator, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
689             result += '</Folder>\n'
690         if not options.output_innerkml:
691             result += KML_FOOTER
692                
693     elif options.format=='track':
694         result = u''
695         if not options.output_innerkml:
696             result += KML_HEADER
697             # don't call STYLE.make_header since there is no icons
698         for mmsi in target_mmsi_iterator:
699             nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
700             result += '<Folder>\n'
701             result += format_boat_track_section(nmea_generator)
702             result += '</Folder>\n'
703         if not options.output_innerkml:
704             result += KML_FOOTER
705
706     else:
707         print >> sys.stderr, 'Unknown output format'
708         sys.exit(1)
709         
710     result = result.encode('utf-8')
711
712     if not options.output_kml:
713         result = kml_to_kmz(result)
714
715     print result
716
717 if __name__ == '__main__':
718     main()