7 from datetime import datetime, time, timedelta
10 from ais.area import load_area_from_kml_polygon
11 from ais.common import *
12 from ais.djais.settings import AIS_BASE_URL
13 from ais.inputs.config import get_hidden_mmsi
14 from ais.ntools import (LatLonFormatError, clean_latitude, clean_longitude,
15 datetime_to_timestamp, mmsi_to_strmmsi,
16 strmmsi_to_mmsi, xml_escape)
18 __all__ = ['format_fleet_lastpos', 'format_boat_intime', 'STYLE', 'KML_DISPLAYOPT_NONAMES', 'KML_DISPLAYOPT_HISTORICAL', 'KML_DISPLAYOPT_SOURCES', 'KML_DISPLAYOPT_SHOWHIDDEN', '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
24 KML_DISPLAYOPT_SHOWHIDDEN = 8 # show hidden ships
27 LOST_PERIOD = timedelta(1)
30 <?xml version="1.0" encoding="UTF-8"?>
31 <kml xmlns="http://www.opengis.net/kml/2.2"
32 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] + list(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] + list(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
219 STYLE = FishersStyle()
222 def format_boat_data(nmea, timeinfo=None, display_options=0):
224 timeinfo: None to generate a GoogleEarth 4 file, with no timeing information
225 True to generate a GoogleEarth 5 file, with start time from nmea
226 datetime or timestamp instance to generate a GoogleEarth 5 file, with start time from nmea and this enddate
229 timestamp_1, status, rot, sog, latitude, longitude, cog, heading, source_1 = Nmea1.to_values(nmea)
230 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)
232 if latitude == AIS_LAT_NOT_AVAILABLE or longitude == AIS_LON_NOT_AVAILABLE:
237 if timeinfo is not None and timeinfo is not True:
238 if not isinstance(timeinfo, datetime):
239 timeinfo = datetime.utcfromtimestamp(timeinfo)
241 result += '<Placemark>\n'
243 if not (display_options & KML_DISPLAYOPT_NONAMES):
244 result += '<name>' + xml_escape(nmea.get_title()) + '</name>\n'
246 result += '<description><![CDATA[\n'
247 if display_options & KML_DISPLAYOPT_NONAMES:
248 result += 'Vessel name: ' + xml_escape(nmea.get_name()) + '<br>\n'
250 dt_1 = datetime.utcfromtimestamp(timestamp_1)
251 if display_options & KML_DISPLAYOPT_HISTORICAL:
252 result += '%s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
256 is_lost = dt_1 < datetime.utcnow() - LOST_PERIOD
258 result += 'Tack <b>lost</b> since %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
260 result += 'Last seen %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
261 else: # timeinfo is not None
265 is_lost = timeinfo > dt_1 + LOST_PERIOD
267 if not mmsi.isdigit():
268 result += 'NO MMSI<br>\n'
269 is_land_station = False
271 result += 'MMSI: %s ' % mmsi
272 ref_mmsi = str(mmsi) # FIXME not needed
273 is_land_station = ref_mmsi.startswith('00') and not ref_mmsi.startswith('000')
275 ref_mmsi = ref_mmsi[2:]
276 result += '(' + COUNTRIES_MID.get(int(ref_mmsi[:3]), 'fake') + ')<br>\n'
277 if not is_land_station:
279 # result += 'IMO<a href="http://www.xvas.it/SPECIAL/VTship.php?imo=%(imo)s&mode=CK">%(imo)s</a><br>\n' % { 'imo': imo }
280 result += 'IMO: %s<br>\n' % imo
282 result += 'no known IMO<br>\n'
283 callsign = nmea.get_callsign(default=None)
284 if callsign is not None:
285 result += 'Callsign: %s<br>\n' % xml_escape(callsign)
287 result += 'Type: %s<br>\n' % SHIP_TYPES.get(type_, 'unknown')
288 if status != AIS_STATUS_NOT_AVAILABLE:
289 result += 'Status: %s<br>\n' % STATUS_CODES.get(status, 'unknown')
290 if cog != AIS_COG_NOT_AVAILABLE:
291 result += 'Course: %.01f°<br>\n' % (cog / 10)
292 if heading != AIS_NO_HEADING:
293 result += 'Heading: %d°<br>\n' % heading
294 if sog != AIS_SOG_NOT_AVAILABLE:
295 if sog != AIS_SOG_FAST_MOVER:
296 result += 'Speed: %.01f kts<br>\n' % (sog / AIS_SOG_SCALE)
298 result += 'Speed: more that than 102.2 kts<br>\n'
299 length = nmea.get_length()
300 width = nmea.get_width()
301 if length or width or draught:
302 result += 'Size: %dx%d' % (length, width)
304 result += '/%.01f' % (draught / 10)
306 destination = nmea.get_destination(default=None)
308 result += 'Destination: %s<br>\n' % xml_escape(destination)
309 eta = nmea.get_eta_str(default=None)
311 result += 'ETA: %s<br>\n' % eta
313 if (display_options & KML_DISPLAYOPT_SOURCES) and (source_1 or source_5):
316 result += Nmea.format_source(source_1)
317 if source_5 and source_1 != source_5:
318 result += ', ' + Nmea.format_source(source_5)
320 result += '<a href="' + AIS_BASE_URL + '/vessel/%(mmsi)s/">More...</a>' \
323 result += '</description>\n'
325 result += '<styleUrl>#%s</styleUrl>\n' \
326 % STYLE.get_style_name(nmea, is_lost)
328 result += '<Point>\n'
329 result += '<coordinates>%s,%s</coordinates>' \
330 % (longitude / AIS_LATLON_SCALE, latitude / AIS_LATLON_SCALE)
331 result += '</Point>\n'
333 if timeinfo is not None:
334 # result += '<TimeStamp><when>%s</when></TimeStamp>\n' \
335 # % (dt_1.strftime('%Y-%m-%dT%H:%M:%SZ'))
336 result += '<gx:TimeSpan><begin>%s</begin>' \
337 % dt_1.strftime('%Y-%m-%dT%H:%M:%SZ')
338 if timeinfo is not True:
339 result += '<end>%s</end>' \
340 % timeinfo.strftime('%Y-%m-%dT%H:%M:%SZ')
341 result += '</gx:TimeSpan>\n'
342 result += '</Placemark>\n'
346 def format_fleet_lastpos(mmsi_iterator, document_name=None, display_options=0):
350 if document_name is None:
351 document_name = 'AIS database'
352 result += '<name>%s</name>\n' % document_name
354 result += STYLE.make_header()
356 long_ago = datetime_to_timestamp(datetime.utcnow() - timedelta(90))
358 for mmsi in mmsi_iterator:
359 nmea = Nmea.new_from_lastinfo(mmsi)
360 if not (display_options & KML_DISPLAYOPT_SHOWHIDDEN) and strmmsi_to_mmsi(mmsi) in get_hidden_mmsi():
361 result += '<Placemark>\n'
362 result += '<name>' + xml_escape(nmea.get_title()) + '</name>\n'
363 result += '<description>Sorry, access to the position of that ship is restricted. It is not available for you.</description>\n'
364 result += '</Placemark>\n'
366 if nmea.get_last_timestamp() < long_ago:
368 result += format_boat_data(nmea, display_options=display_options | KML_DISPLAYOPT_SOURCES)
373 def format_boat_intime_section(nmea_iterator, kml_displayopt=0):
376 for nmea in nmea_iterator:
377 if last_nmea is None:
380 timeinfo = datetime.utcfromtimestamp(last_nmea.timestamp_1)
382 result += format_boat_data(nmea, timeinfo,
383 kml_displayopt | KML_DISPLAYOPT_HISTORICAL)
384 # make a copy because nmea will be patched with new data:
385 last_nmea = copy.copy(nmea)
387 result += '<description>Vessel not found</description>'
391 def format_boat_intime(nmea_iterator):
394 result += STYLE.make_header()
395 result += format_boat_intime_section(nmea_iterator)
400 def format_boat_track_section(nmea_iterator, name=''):
401 strcoordinates = '<Placemark>\n<LineString>\n<coordinates>\n'
403 for nmea in nmea_iterator:
405 name = nmea.get_title()
406 if nmea.longitude != AIS_LON_NOT_AVAILABLE and nmea.latitude != AIS_LAT_NOT_AVAILABLE:
407 if segment_length > 65000:
408 logging.debug('Line is too long. Spliting.')
409 strcoordinates += ' %.8f,%.8f' \
410 % (nmea.longitude / AIS_LATLON_SCALE,
411 nmea.latitude / AIS_LATLON_SCALE)
412 strcoordinates += '</coordinates>\n</LineString>\n</Placemark>\n<Placemark>\n<LineString>\n<coordinates>\n'
416 strcoordinates += ' %.8f,%.8f' \
417 % (nmea.longitude / AIS_LATLON_SCALE,
418 nmea.latitude / AIS_LATLON_SCALE)
419 strcoordinates += '</coordinates>\n</LineString></Placemark>\n'
422 result += '<name>%s track</name>\n' % name
423 if len(strcoordinates) > 39 + 2 * (1 + 12 + 1 + 11) + 42 + 1:
424 result += strcoordinates
426 result += '<description>No data available</description>\n'
430 def kml_to_kmz(inputstr):
431 if type(inputstr) == str:
432 inputstr = inputstr.encode('utf-8')
434 kmz = zipfile.ZipFile(output, 'w')
435 kmz.writestr('doc.kml', inputstr)
436 for iconname in STYLE.used_icons:
437 kmz.write('/usr/share/ais/kmz_icons/' + iconname, iconname)
439 return output.getvalue()
444 from optparse import OptionParser, OptionGroup
446 parser = OptionParser(usage='%prog [options] { mmsi | @fleetname | #fleetid }+ | all')
448 parser.add_option('-d', '--debug',
449 action='store_true', dest='debug', default=False,
452 parser.add_option('-e', '--end',
453 action='store', dest='sdt_end', metavar="'YYYYMMDD HHMMSS'",
454 help='End data processing on that GMT date time.'
456 ' If a date is provided without time, time defaults to 235959.')
457 parser.add_option('-s', '--start',
458 action='store', dest='sdt_start', metavar="'YYYYMMDD HHMMSS'",
459 help='Start data processing on that date.'
460 ' Using that option enables multiple output of the same boat.'
461 ' Disabled by default.'
462 ' If a date is provided without time, time default to 000000.'
463 ' If other options enable multiple output, default to 1 day before'
465 parser.add_option('--duration',
466 action='store', dest='sdt_duration', metavar="DURATION",
467 help='Duration of reference period.'
468 ' Last character may be S for seconds, M(inutes), D(ays), W(eeks)'
469 ' Default is seconds.'
470 ' This is the time length bewteen --start and --end above.'
471 ' If you want multiple output of the same boat, you may use '
472 ' --start, --end or --duration, 2 of them, but not 3 of them.')
473 parser.add_option('-g', '--granularity',
474 action='store', type='int', dest='granularity', metavar='SECONDS',
475 help='Dump only one position every granularity seconds.'
476 'Using that option enables multiple output of the same boat.'
477 'If other options enable multiple output, defaults to 600'
479 parser.add_option('--max',
480 action='store', type='int', dest='max_count', metavar='NUMBER',
481 help='Dump a maximum of NUMBER positions every granularity seconds.'
482 'Using that option enables multiple output of the same boat.')
484 parser.add_option('--show-hidden-ships',
485 action='store_true', dest='show_hidden_ships', default=False,
486 help='Include hidden ships in results')
488 parser.add_option('--filter-knownposition',
489 action='store_true', dest='filter_knownposition', default=False,
490 help="eliminate unknown positions from results.")
492 parser.add_option('--filter-speedcheck',
493 action='store', type='int', dest='speedcheck', default=200, metavar='KNOTS',
494 help='Eliminate erroneaous positions from results,'
495 ' based on impossible speed.')
497 parser.add_option('--filter-type',
498 action='append', type='int', dest='type_list', metavar="TYPE",
499 help="process a specific ship type.")
500 parser.add_option('--help-types',
501 action='store_true', dest='help_types', default=False,
502 help="display list of available types")
504 parser.add_option('--filter-area',
505 action='store', type='str', dest='area_file', metavar="FILE.KML",
506 help="only process a specific area as defined in a kml polygon file.")
507 parser.add_option('--filter-farfrom',
508 action='store', dest='far_from', nargs=3, metavar='LAT LONG MILES',
509 help="only show ships farther than MILES miles from LAT,LONG")
510 parser.add_option('--filter-closeto',
511 action='store', dest='close_to', nargs=3, metavar='LAT LONG MILES',
512 help="only show ships closer than MILES miles from LAT,LONG")
513 parser.add_option('--filter-sog-le',
514 action='store', dest='sog_le', metavar='KNOTS',
515 help='only show ships when speed over ground is less or equal to KNOTS.')
518 parser.add_option('--format',
519 choices=('positions', 'track', 'animation'), dest='format', default='positions',
520 help="select output format: positions(*) or track or animation")
522 parser.add_option('--kml',
523 action='store_true', dest='output_kml', default=False,
524 help="Output a kml file. Default is output of a kmz file with icons")
525 parser.add_option('--inner-kml',
526 action='store_true', dest='output_innerkml', default=False,
527 help='Output a kml fragment file without the <Document> wrappers.\n'
528 'File should be reproccessed to be usefull. That option implies --kml')
530 parser.add_option('--style',
531 choices=('fishers', 'pelagos'), dest='style', default='fishers',
532 help='select one of the predefined style for display: '
533 'fishers(*) or pelagos')
535 parser.add_option('--icon-size',
536 action='store', type='float', dest='icon_size', metavar='SCALE', default=0.5,
537 help='Set icons size. Default = %default')
538 parser.add_option('--label-size',
539 action='store', type='float', dest='label_size', metavar='SCALE', default=0.7,
540 help='Set label scale. Default = %default')
542 parser.add_option('--no-names',
543 action='store_const', const=KML_DISPLAYOPT_NONAMES,
544 dest='kml_displayopt_noname', default=0,
545 help="don't show ship names")
547 parser.add_option('--show-sources',
548 action='store_const', const=KML_DISPLAYOPT_SOURCES,
549 dest='kml_displayopt_sources', default=0,
550 help="show information source")
554 expert_group = OptionGroup(parser, "Expert Options",
555 "You normaly don't need any of these")
557 expert_group.add_option('--db',
558 action='store', dest='db', default=DBPATH,
559 help="path to filesystem database. Default=%default")
560 parser.add_option_group(expert_group)
562 options, args = parser.parse_args()
564 if options.help_types:
565 keys = SHIP_TYPES.keys()
568 print(k, SHIP_TYPES[k])
574 loglevel = logging.DEBUG
576 loglevel = logging.INFO
577 logging.basicConfig(level=loglevel, format='%(asctime)s %(levelname)s %(message)s')
584 print("No ship to process", file=sys.stderr)
587 target_mmsi_iterator = []
592 elif arg.startswith('@'):
593 target_mmsi_iterator += load_fleet_to_uset(fleetname_to_fleetid(arg[1:]))
594 elif arg.startswith('^'):
595 target_mmsi_iterator += load_fleet_to_uset(int(arg[1:]))
597 target_mmsi_iterator.append(arg)
599 if target_mmsi_iterator:
600 logging.warning('Selecting all ships, ignoring other arguments')
601 target_mmsi_iterator = all_mmsi_generator()
603 if not options.show_hidden_ships:
604 target_mmsi_iterator = mmsiiterator_nohiddenship(target_mmsi_iterator)
610 if options.sdt_start:
611 # remove non digit characters
612 options.sdt_start = "".join([c for c in options.sdt_start if c.isdigit()])
613 if len(options.sdt_start) == 14:
614 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
615 elif len(options.sdt_start) == 8:
616 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d')
618 print("Invalid format for --start option", file=sys.stderr)
622 # remove non digit characters
623 options.sdt_end = "".join([c for c in options.sdt_end if c.isdigit()])
624 if len(options.sdt_end) == 14:
625 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d%H%M%S')
626 elif len(options.sdt_end) == 8:
627 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d')
628 options.sdt_end = datetime.combine(options.sdt_end.date(), time(23, 59, 59))
630 print("Invalid format for --end option", file=sys.stderr)
633 if options.sdt_duration:
635 options.sdt_duration = options.sdt_duration.replace(' ', '')
637 options.sdt_duration = options.sdt_duration.upper()
638 if options.sdt_duration[-1] == 'S':
639 options.sdt_duration = options.sdt_duration[:-1]
641 elif options.sdt_duration[-1] == 'M':
642 options.sdt_duration = options.sdt_duration[:-1]
644 elif options.sdt_duration[-1] == 'H':
645 options.sdt_duration = options.sdt_duration[:-1]
646 duration_unit = 60 * 60
647 elif options.sdt_duration[-1] == 'D':
648 options.sdt_duration = options.sdt_duration[:-1]
649 duration_unit = 24 * 60 * 60
650 elif options.sdt_duration[-1] == 'W':
651 options.sdt_duration = options.sdt_duration[:-1]
652 duration_unit = 7 * 24 * 60 * 60
656 options.sdt_duration = long(options.sdt_duration)
658 print("Can't parse duration", file=sys.stderr)
660 options.sdt_duration = timedelta(0, options.sdt_duration * duration_unit)
662 if options.sdt_start or options.sdt_duration or options.granularity is not None or options.max_count:
663 # Time period is enabled (note that date_end only defaults to one day archives ending then)
664 if not options.sdt_start and not options.sdt_end and not options.sdt_duration:
665 options.sdt_duration = timedelta(1) # One day
666 # continue without else
667 if not options.sdt_start and not options.sdt_end and options.sdt_duration:
668 dt_end = datetime.utcnow()
669 dt_start = dt_end - options.sdt_duration
670 # elif not options.sdt_start and options.sdt_end and not options.sdt_duration:
672 elif not options.sdt_start and options.sdt_end and options.sdt_duration:
673 dt_end = options.sdt_end
674 dt_start = dt_end - options.sdt_duration
675 elif options.sdt_start and not options.sdt_end and not options.sdt_duration:
676 dt_start = options.sdt_start
677 dt_end = datetime.utcnow()
678 elif options.sdt_start and not options.sdt_end and options.sdt_duration:
679 dt_start = options.sdt_start
680 dt_end = dt_start + options.sdt_duration
681 elif options.sdt_start and options.sdt_end and not options.sdt_duration:
682 dt_start = options.sdt_start
683 dt_end = options.sdt_end
685 assert options.sdt_start and options.sdt_end and options.sdt_duration, 'Internal error'
686 print("You can't have all 3 --start --end and --duration", file=sys.stderr)
688 if options.granularity is None:
689 options.granularity = 600
691 # Only get one position
694 dt_end = options.sdt_end
696 dt_end = datetime.utcnow()
697 options.max_count = 1
698 if options.granularity is None:
699 options.granularity = 600
701 logging.debug('--start is %s', dt_start)
702 logging.debug('--end is %s', dt_end)
710 if options.filter_knownposition:
711 filters.append(filter_knownposition)
713 if options.speedcheck != 0:
714 maxmps = options.speedcheck / 3600 # from knots to NM per seconds
715 filters.append(lambda nmea: filter_speedcheck(nmea, maxmps))
717 if options.area_file:
718 area = load_area_from_kml_polygon(options.area_file)
719 filters.append(lambda nmea: filter_area(nmea, area))
723 lat = clean_latitude(unicode(options.close_to[0], 'utf-8'))
724 lon = clean_longitude(unicode(options.close_to[1], 'utf-8'))
725 except LatLonFormatError as err:
726 print(err.args, file=sys.stderr)
728 miles = float(options.close_to[2])
729 filters.append(lambda nmea: filter_close_to(nmea, lat, lon, miles))
733 lat = clean_latitude(unicode(options.far_from[0], 'utf-8'))
734 lon = clean_longitude(unicode(options.far_from[1], 'utf-8'))
735 except LatLonFormatError as err:
736 print(err.args, file=sys.stderr)
738 miles = float(options.far_from[2])
739 filters.append(lambda nmea: filter_far_from(nmea, lat, lon, miles))
742 filters.append(lambda nmea: filter_sog_le(nmea, float(options.sog_le)))
744 if options.type_list:
745 def filter_type(nmea):
746 # print(nmea.type, repr(options.type_list), nmea.type in options.type_list)
747 # print(repr(nmea.get_dump_row()))
748 return nmea.type in options.type_list
749 filters.append(filter_type)
755 if options.style == 'pelagos':
756 STYLE = PelagosStyle()
758 kml_displayopt = options.kml_displayopt_noname | options.kml_displayopt_sources
760 STYLE.icon_size = options.icon_size
761 STYLE.label_size = options.label_size
763 if options.output_innerkml:
764 options.output_kml = True
769 if options.format == 'positions':
771 if not options.output_innerkml:
773 result += STYLE.make_header()
774 for mmsi in target_mmsi_iterator:
775 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
776 for nmea in nmea_generator:
777 result += format_boat_data(nmea, None, kml_displayopt | KML_DISPLAYOPT_HISTORICAL)
778 if not options.output_innerkml:
781 elif options.format == 'animation':
783 if not options.output_innerkml:
785 result += STYLE.make_header()
786 for mmsi in target_mmsi_iterator:
787 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
788 result += '<Folder>\n'
790 # result += '<name>' + xml_escape(nmea.get_title()) + '</name>\n'
791 result += format_boat_intime_section(nmea_generator, kml_displayopt | KML_DISPLAYOPT_HISTORICAL)
792 result += '</Folder>\n'
793 if not options.output_innerkml:
796 elif options.format == 'track':
798 if not options.output_innerkml:
800 # don't call STYLE.make_header since there is no icons
801 for mmsi in target_mmsi_iterator:
802 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
803 # result += '<Folder>\n'
804 result += format_boat_track_section(nmea_generator)
805 # result += '</Folder>\n'
806 if not options.output_innerkml:
810 print('Unknown output format', file=sys.stderr)
813 result = result.encode('utf-8')
815 if not options.output_kml:
816 result = kml_to_kmz(result)
821 if __name__ == '__main__':