2 # -*- coding: utf-8 -*-
4 from __future__ import division
8 from StringIO import StringIO # TODO use python 2.6 io.BufferedWrite(sys.stdout, )
9 from datetime import datetime, timedelta, time
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
16 from ais.djais.settings import AIS_BASE_URL
18 __all__ = [ 'format_fleet', 'format_boat_intime', 'format_boat_track', 'STYLE', 'KML_DISPLAYOPT_NONAMES', 'KML_DISPLAYOPT_HISTORICAL', 'KML_DISPLAYOPT_SOURCES', 'kml_to_kmz' ]
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
26 LOST_PERIOD = timedelta(1)
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">
44 KML style for ship presentation.
45 It contains a list of png icons that were used.
46 This is a virtual class.
50 self.icon_size = 0.5 # 0.2
51 self.used_icons = set()
53 def _format_style(self, stylename, icon, heading=None, color=None):
55 color format is google styled: aabbggrr
56 example ffea00ff for purple
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'
67 result += ' <href>%s</href>\n' % icon
68 result += ' </Icon>\n'
69 if heading is not None:
70 result += ' <heading>%d</heading>\n' % heading
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)
80 def make_header(self):
81 raise NotImplementedError # abstract class
83 def get_style_name(self, nmea, is_lost):
85 Returns the name of the style based on nmea data
86 and whether the ship was seen recently or not.
88 raise NotImplementedError # abstract class
91 class FishersStyle(Style):
92 def make_header(self):
94 green = 'ff86fd5f' # '5f-fd-86'
95 yellow = 'ff86eeff' #'ff-ee-86'
96 red = 'ff5865fc' #'fc-65-58'
98 result += self._format_style('landstation', 'capital_small.png')
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)
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)
117 def get_style_name(self, nmea, is_lost):
119 Returns the name of the style based on nmea data
120 and whether the ship was seen recently or not.
122 if nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000'):
125 if nmea.type == 30: # Fisher ship
127 elif nmea.type in (31, 32, 52): # Towing or Tug
129 elif nmea.type in (35, 53, 55): # Authority
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:
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
151 class PelagosStyle(Style):
152 def make_header(self):
154 green = 'ff86fd5f' # '5f-fd-86'
155 yellow = 'ff86eeff' #'ff-ee-86'
156 pink = 'ffff00ea' #'ea-00-ff'
157 red = 'ff5865fc' #'fc-65-58'
160 result += self._format_style('landstation', 'capital_small.png')
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)
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)
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)
183 def get_style_name(self, nmea, is_lost):
185 Returns the name of the style based on nmea data
186 and whether the ship was seen recently or not.
188 if (nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000')):
191 if nmea.type in (41, 61, 71, 81): # major hazard materials
192 stylename = 'hazarda'
193 elif nmea.type >= 70 and nmea.type <= 79:
195 elif nmea.type >= 80 and nmea.type <= 89:
197 elif nmea.type >= 40 and nmea.type <= 49:
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:
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
218 STYLE = FishersStyle()
221 def format_boat_data(nmea, timeinfo=None, display_options=0):
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
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)
231 if latitude == AIS_LAT_NOT_AVAILABLE or longitude == AIS_LON_NOT_AVAILABLE:
236 if timeinfo is not None and timeinfo != True:
237 if not isinstance(timeinfo, datetime):
238 timeinfo = datetime.utcfromtimestamp(timeinfo)
240 result += u'<Placemark>\n'
242 if not (display_options & KML_DISPLAYOPT_NONAMES):
243 result += u'<name>' + xml_escape(nmea.get_title()) + u'</name>\n'
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'
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')
255 is_lost = dt_1 < datetime.utcnow()-LOST_PERIOD
257 result += u'Tack <b>lost</b> since %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
259 result += u'Last seen %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
260 else: # timeinfo is not None
264 is_lost = timeinfo > dt_1 + LOST_PERIOD
266 if not mmsi.isdigit():
267 result += u'NO MMSI<br>\n'
268 is_land_station = False
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')
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 :
278 #result += u'IMO<a href="http://www.xvas.it/SPECIAL/VTship.php?imo=%(imo)s&mode=CK">%(imo)s</a><br>\n' % { 'imo': imo }
279 result += u'IMO: %s<br>\n' % imo
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)
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)
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)
303 result += u'/%.01f' % (draught/10.)
305 destination = nmea.get_destination(default=None)
307 result += u'Destination: %s<br>\n' % xml_escape(destination)
308 eta = nmea.get_eta_str(default=None)
310 result += u'ETA: %s<br>\n' % eta
312 if (display_options & KML_DISPLAYOPT_SOURCES) and (source_1 or source_5):
313 result += u'Source: '
315 result += Nmea.format_source(source_1)
316 if source_5 and source_1 != source_5:
317 result += u', '+ Nmea.format_source(source_5)
319 result += u'<a href="' + AIS_BASE_URL + u'/vessel/%(mmsi)s/">More...</a>' \
322 result += u'</description>\n'
324 result += u'<styleUrl>#%s</styleUrl>\n' \
325 % STYLE.get_style_name(nmea, is_lost)
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'
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')
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'
347 def format_fleet(mmsi_iterator, document_name=None):
351 if document_name is None:
352 document_name = 'AIS database'
353 result += u'<name>%s</name>\n' % document_name
355 result += STYLE.make_header()
357 long_ago = datetime_to_timestamp(datetime.utcnow() - timedelta(90))
359 for mmsi in mmsi_iterator:
360 nmea = Nmea.new_from_lastinfo(mmsi)
361 if nmea.get_last_timestamp() < long_ago:
363 result += format_boat_data(nmea, display_options=KML_DISPLAYOPT_SOURCES)
368 def format_boat_intime_section(nmea_iterator, kml_displayopt=0):
371 for nmea in nmea_iterator:
372 if last_nmea is None:
375 timeinfo = datetime.utcfromtimestamp(last_nmea.timestamp_1)
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)
382 result += u'<description>Vessel not found</description>'
386 def format_boat_intime(nmea_iterator):
389 result += STYLE.make_header()
390 result += format_boat_intime_section(nmea_iterator)
395 def format_boat_track_section(nmea_iterator, name=u''):
396 strcoordinates = '<Placemark>\n<LineString>\n<coordinates>\n'
398 for nmea in nmea_iterator:
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'
411 strcoordinates += ' %.8f,%.8f' \
412 % (nmea.longitude/AIS_LATLON_SCALE,
413 nmea.latitude/AIS_LATLON_SCALE)
414 strcoordinates += '</coordinates>\n</LineString></Placemark>\n'
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)
421 result += u'<description>No data available</description>\n'
425 def format_boat_track(nmea_iterator):
428 #result += STYLE.make_header()
429 result += format_boat_track_section(nmea_iterator)
435 def kml_to_kmz(inputstr):
436 if isinstance(inputstr, unicode):
437 inputstr = inputstr.encode('utf-8')
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)
444 return output.getvalue()
449 from optparse import OptionParser, OptionGroup
451 parser = OptionParser(usage='%prog [options] { mmsi | @fleetname | #fleetid }+ | all')
453 parser.add_option('-d', '--debug',
454 action='store_true', dest='debug', default=False,
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.'
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'
470 parser.add_option('--duration',
471 action='store', dest='sdt_duration', metavar="DURATION",
472 help='Duration of reference period.'
473 ' Last character may be S for seconds, M(inutes), D(ays), W(eeks)'
474 ' Default is seconds.'
475 ' This is the time length bewteen --start and --end above.'
476 ' If you want multiple output of the same boat, you may use '
477 ' --start, --end or --duration, 2 of them, but not 3 of them.')
478 parser.add_option('-g', '--granularity',
479 action='store', type='int', dest='granularity', metavar='SECONDS',
480 help='Dump only one position every granularity seconds.'
481 'Using that option enables multiple output of the same boat.'
482 'If other options enable multiple output, defaults to 600'
484 parser.add_option('--max',
485 action='store', type='int', dest='max_count', metavar='NUMBER',
486 help='Dump a maximum of NUMBER positions every granularity seconds.'
487 'Using that option enables multiple output of the same boat.')
489 parser.add_option('--filter-knownposition',
490 action='store_true', dest='filter_knownposition', default=False,
491 help="eliminate unknown positions from results.")
493 parser.add_option('--filter-speedcheck',
494 action='store', type='int', dest='speedcheck', default=200, metavar='KNOTS',
495 help='Eliminate erroneaous positions from results,'
496 ' based on impossible speed.')
498 parser.add_option('--filter-type',
499 action='append', type='int', dest='type_list', metavar="TYPE",
500 help="process a specific ship type.")
501 parser.add_option('--help-types',
502 action='store_true', dest='help_types', default=False,
503 help="display list of available types")
505 parser.add_option('--filter-area',
506 action='store', type='str', dest='area_file', metavar="FILE.KML",
507 help="only process a specific area as defined in a kml polygon file.")
508 parser.add_option('--filter-farfrom',
509 action='store', dest='far_from', nargs=3, metavar='LAT LONG MILES',
510 help="only show ships farther than MILES miles from LAT,LONG")
511 parser.add_option('--filter-closeto',
512 action='store', dest='close_to', nargs=3, metavar='LAT LONG MILES',
513 help="only show ships closer than MILES miles from LAT,LONG")
514 parser.add_option('--filter-sog-le',
515 action='store', dest='sog_le', metavar='KNOTS',
516 help='only show ships when speed over ground is less or equal to KNOTS.')
519 parser.add_option('--format',
520 choices=('positions', 'track', 'animation'), dest='format', default='positions',
521 help="select output format: positions(*) or track or animation")
523 parser.add_option('--kml',
524 action='store_true', dest='output_kml', default=False,
525 help="Output a kml file. Default is output of a kmz file with icons")
526 parser.add_option('--inner-kml',
527 action='store_true', dest='output_innerkml', default=False,
528 help='Output a kml fragment file without the <Document> wrappers.\n'
529 'File should be reproccessed to be usefull. That option implies --kml')
531 parser.add_option('--style',
532 choices=('fishers', 'pelagos'), dest='style', default='fishers',
533 help='select one of the predefined style for display: '
534 'fishers(*) or pelagos')
536 parser.add_option('--icon-size',
537 action='store', type='float', dest='icon_size', metavar='SCALE', default=0.5,
538 help='Set icons size. Default = %default')
540 parser.add_option('--no-names',
541 action='store_const', const=KML_DISPLAYOPT_NONAMES,
542 dest='kml_displayopt_noname', default=0,
543 help="don't show ship names")
545 parser.add_option('--show-sources',
546 action='store_const', const=KML_DISPLAYOPT_SOURCES,
547 dest='kml_displayopt_sources', default=0,
548 help="show information source")
552 expert_group = OptionGroup(parser, "Expert Options",
553 "You normaly don't need any of these")
555 expert_group.add_option('--db',
556 action='store', dest='db', default=DBPATH,
557 help="path to filesystem database. Default=%default")
558 parser.add_option_group(expert_group)
560 options, args = parser.parse_args()
563 if options.help_types:
564 keys = SHIP_TYPES.keys()
567 print k, SHIP_TYPES[k]
573 loglevel = logging.DEBUG
575 loglevel = logging.INFO
576 logging.basicConfig(level=loglevel, format='%(asctime)s %(levelname)s %(message)s')
583 print >> sys.stderr, "No ship to process"
586 target_mmsi_iterator = []
591 elif arg.startswith('@'):
592 target_mmsi_iterator += load_fleet_to_uset(fleetname_to_fleetid(arg[1:]))
593 elif arg.startswith('^'):
594 target_mmsi_iterator += load_fleet_to_uset(int(arg[1:]))
596 target_mmsi_iterator.append(arg)
598 if target_mmsi_iterator:
599 logging.warning('Selecting all ships, ignoring other arguments')
600 target_mmsi_iterator = all_mmsi_generator()
606 if options.sdt_start:
607 # remove non digit characters
608 options.sdt_start = "".join([ c for c in options.sdt_start if c.isdigit()])
609 if len(options.sdt_start)==14:
610 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
611 elif len(options.sdt_start)==8:
612 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d')
614 print >> sys.stderr, "Invalid format for --start option"
618 # remove non digit characters
619 options.sdt_end = "".join([ c for c in options.sdt_end if c.isdigit()])
620 if len(options.sdt_end)==14:
621 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d%H%M%S')
622 elif len(options.sdt_end)==8:
623 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d')
624 options.sdt_end = datetime.combine(options.sdt_end.date(), time(23, 59, 59))
626 print >> sys.stderr, "Invalid format for --end option"
629 if options.sdt_duration:
631 options.sdt_duration = options.sdt_duration.replace(' ', '')
633 options.sdt_duration = options.sdt_duration.upper()
634 if options.sdt_duration[-1] == 'S':
635 options.sdt_duration = options.sdt_duration[:-1]
637 elif options.sdt_duration[-1] == 'M':
638 options.sdt_duration = options.sdt_duration[:-1]
640 elif options.sdt_duration[-1] == 'H':
641 options.sdt_duration = options.sdt_duration[:-1]
642 duration_unit = 60*60
643 elif options.sdt_duration[-1] == 'D':
644 options.sdt_duration = options.sdt_duration[:-1]
645 duration_unit = 24*60*60
646 elif options.sdt_duration[-1] == 'W':
647 options.sdt_duration = options.sdt_duration[:-1]
648 duration_unit = 7*24*60*60
652 options.sdt_duration = long(options.sdt_duration)
654 print >> sys.stderr, "Can't parse duration"
656 options.sdt_duration = timedelta(0, options.sdt_duration * duration_unit)
658 if options.sdt_start or options.sdt_duration or options.granularity is not None or options.max_count:
659 # Time period is enabled (note that date_end only defaults to one day archives ending then)
660 if not options.sdt_start and not options.sdt_end and not options.sdt_duration:
661 options.sdt_duration = timedelta(1) # One day
662 # continue without else
663 if not options.sdt_start and not options.sdt_end and options.sdt_duration:
664 dt_end = datetime.utcnow()
665 dt_start = dt_end - options.sdt_duration
666 #elif not options.sdt_start and options.sdt_end and not options.sdt_duration:
668 elif not options.sdt_start and options.sdt_end and options.sdt_duration:
669 dt_end = options.sdt_end
670 dt_start = dt_end - options.sdt_duration
671 elif options.sdt_start and not options.sdt_end and not options.sdt_duration:
672 dt_start = options.sdt_start
673 dt_end = datetime.utcnow()
674 elif options.sdt_start and not options.sdt_end and options.sdt_duration:
675 dt_start = options.sdt_start
676 dt_end = dt_start + options.sdt_duration
677 elif options.sdt_start and options.sdt_end and not options.sdt_duration:
678 dt_start = options.sdt_start
679 dt_end = options.sdt_end
681 assert options.sdt_start and options.sdt_end and options.sdt_duration, 'Internal error'
682 print >> sys.stderr, "You can't have all 3 --start --end and --duration"
684 if options.granularity is None:
685 options.granularity = 600
687 # Only get one position
690 dt_end = options.sdt_end
692 dt_end = datetime.utcnow()
693 options.max_count = 1
694 if options.granularity is None:
695 options.granularity = 600
697 logging.debug('--start is %s', dt_start)
698 logging.debug('--end is %s', dt_end)
706 if options.filter_knownposition:
707 filters.append(filter_knownposition)
709 if options.speedcheck != 0:
710 maxmps = options.speedcheck / 3600. # from knots to NM per seconds
711 filters.append(lambda nmea: filter_speedcheck(nmea, maxmps))
713 if options.area_file:
714 area = load_area_from_kml_polygon(options.area_file)
715 filters.append(lambda nmea: filter_area(nmea, area))
719 lat = clean_latitude(unicode(options.close_to[0], 'utf-8'))
720 lon = clean_longitude(unicode(options.close_to[1], 'utf-8'))
721 except LatLonFormatError as err:
722 print >> sys.stderr, err.args
724 miles = float(options.close_to[2])
725 filters.append(lambda nmea: filter_close_to(nmea, lat, lon, miles))
729 lat = clean_latitude(unicode(options.far_from[0], 'utf-8'))
730 lon = clean_longitude(unicode(options.far_from[1], 'utf-8'))
731 except LatLonFormatError as err:
732 print >> sys.stderr, err.args
734 miles = float(options.far_from[2])
735 filters.append(lambda nmea: filter_far_from(nmea, lat, lon, miles))
738 filters.append(lambda nmea: filter_sog_le(nmea, float(options.sog_le)))
740 if options.type_list:
741 def filter_type(nmea):
742 #print nmea.type, repr(options.type_list), nmea.type in options.type_list
743 #print repr(nmea.get_dump_row())
744 return nmea.type in options.type_list
745 filters.append(filter_type)
751 if options.style == 'pelagos':
752 STYLE = PelagosStyle()
754 kml_displayopt = options.kml_displayopt_noname | options.kml_displayopt_sources
756 STYLE.icon_size = options.icon_size
758 if options.output_innerkml:
759 options.output_kml = True
764 if options.format == 'positions':
766 if not options.output_innerkml:
768 result += STYLE.make_header()
769 for mmsi in target_mmsi_iterator:
770 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
771 for nmea in nmea_generator:
772 result += format_boat_data(nmea, None, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
773 if not options.output_innerkml:
776 elif options.format=='animation':
778 if not options.output_innerkml:
780 result += STYLE.make_header()
781 for mmsi in target_mmsi_iterator:
782 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
783 result += '<Folder>\n'
784 result += format_boat_intime_section(nmea_generator, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
785 result += '</Folder>\n'
786 if not options.output_innerkml:
789 elif options.format=='track':
791 if not options.output_innerkml:
793 # don't call STYLE.make_header since there is no icons
794 for mmsi in target_mmsi_iterator:
795 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
796 result += '<Folder>\n'
797 result += format_boat_track_section(nmea_generator)
798 result += '</Folder>\n'
799 if not options.output_innerkml:
803 print >> sys.stderr, 'Unknown output format'
806 result = result.encode('utf-8')
808 if not options.output_kml:
809 result = kml_to_kmz(result)
813 if __name__ == '__main__':