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, LatLonFormatError, clean_latitude, clean_longitude
16 from ais.inputs.config import get_hidden_mmsi
17 from ais.djais.settings import AIS_BASE_URL
19 __all__ = [ 'format_fleet_lastpos', 'format_boat_intime', 'STYLE', 'KML_DISPLAYOPT_NONAMES', 'KML_DISPLAYOPT_HISTORICAL', 'KML_DISPLAYOPT_SOURCES', 'KML_DISPLAYOPT_SHOWHIDDEN', 'kml_to_kmz' ]
22 KML_DISPLAYOPT_NONAMES = 1 # don't print ship name
23 KML_DISPLAYOPT_HISTORICAL = 2 # never show ship track as lost
24 KML_DISPLAYOPT_SOURCES = 4 # display sources
25 KML_DISPLAYOPT_SHOWHIDDEN = 8 # show hidden ships
28 LOST_PERIOD = timedelta(1)
31 <?xml version="1.0" encoding="UTF-8"?>
32 <kml xmlns="http://www.opengis.net/kml/2.2"
33 xmlns:gx="http://www.google.com/kml/ext/2.2">
46 KML style for ship presentation.
47 It contains a list of png icons that were used.
48 This is a virtual class.
52 self.icon_size = 0.5 # 0.2
53 self.used_icons = set()
55 def _format_style(self, stylename, icon, heading=None, color=None):
57 color format is google styled: aabbggrr
58 example ffea00ff for purple
61 if heading is not None:
62 stylename += '-' + str(heading)
63 result += '<Style id="%s">\n' % stylename
64 result += ' <LabelStyle>\n'
65 result += ' <scale>%f</scale>\n' % self.label_size
66 result += ' </LabelStyle>\n'
67 result += ' <IconStyle>\n'
69 result += ' <href>%s</href>\n' % icon
70 result += ' </Icon>\n'
71 if heading is not None:
72 result += ' <heading>%d</heading>\n' % heading
74 result += ' <color>%s</color>\n' % color
75 result += ' <scale>%f</scale>\n' % self.icon_size
76 result += ' <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>\n'
77 result += ' </IconStyle>\n'
78 result += '</Style>\n'
79 self.used_icons.add(icon)
82 def make_header(self):
83 raise NotImplementedError # abstract class
85 def get_style_name(self, nmea, is_lost):
87 Returns the name of the style based on nmea data
88 and whether the ship was seen recently or not.
90 raise NotImplementedError # abstract class
93 class FishersStyle(Style):
94 def make_header(self):
96 green = 'ff86fd5f' # '5f-fd-86'
97 yellow = 'ff86eeff' #'ff-ee-86'
98 red = 'ff5865fc' #'fc-65-58'
100 result += self._format_style('landstation', 'capital_small.png')
102 result += self._format_style('base-stop', 'boat-stop.png', color=white)
103 result += self._format_style('fisher-stop', 'boat-stop.png', color=red)
104 result += self._format_style('tug-stop', 'boat-stop.png', color=green)
105 result += self._format_style('auth-stop', 'boat-stop.png', color=yellow)
107 for heading in [ None ] + range(0, 360, 10):
108 result += self._format_style('base', 'boat.png', color=white, heading=heading)
109 result += self._format_style('fisher', 'boat.png', color=red, heading=heading)
110 result += self._format_style('tug', 'boat.png', color=green, heading=heading)
111 result += self._format_style('auth', 'boat.png', color=yellow, heading=heading)
112 result += self._format_style('base-lost', 'boat-invis.png', color=white, heading=heading)
113 result += self._format_style('fisher-lost', 'boat-invis.png', color=red, heading=heading)
114 result += self._format_style('tug-lost', 'boat-invis.png', color=green, heading=heading)
115 result += self._format_style('auth-lost', 'boat-invis.png', color=yellow, heading=heading)
119 def get_style_name(self, nmea, is_lost):
121 Returns the name of the style based on nmea data
122 and whether the ship was seen recently or not.
124 if nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000'):
127 if nmea.type == 30: # Fisher ship
129 elif nmea.type in (31, 32, 52): # Towing or Tug
131 elif nmea.type in (35, 53, 55): # Authority
136 if (nmea.status in (1, 5, 6) and (nmea.sog == AIS_SOG_NOT_AVAILABLE or nmea.sog<0.5*AIS_SOG_SCALE)) \
137 or nmea.sog<1*AIS_SOG_SCALE:
143 if nmea.cog != AIS_COG_NOT_AVAILABLE:
144 course = int(nmea.cog/10.) # ais format correction
145 course = (course+5)//10*10 % 360 # go to neareast 10°
146 stylename += '-%d' % course
147 elif nmea.heading != AIS_NO_HEADING:
148 course = (nmea.heading+5)//10*10 % 360 # go to neareast 10°
149 stylename += '-%d' % course
153 class PelagosStyle(Style):
154 def make_header(self):
156 green = 'ff86fd5f' # '5f-fd-86'
157 yellow = 'ff86eeff' #'ff-ee-86'
158 pink = 'ffff00ea' #'ea-00-ff'
159 red = 'ff5865fc' #'fc-65-58'
162 result += self._format_style('landstation', 'capital_small.png')
164 result += self._format_style('base-stop', 'boat-stop.png', color=white)
165 result += self._format_style('cargo-stop', 'boat-stop.png', color=green)
166 result += self._format_style('tanker-stop', 'boat-stop.png', color=yellow)
167 result += self._format_style('hsc-stop', 'boat-stop.png', color=pink)
168 result += self._format_style('hazarda-stop', 'boat-stop.png', color=red)
170 for heading in [ None ] + range(0, 360, 10):
171 result += self._format_style('base', 'boat.png', color=white, heading=heading)
172 result += self._format_style('cargo', 'boat.png', color=green, heading=heading)
173 result += self._format_style('tanker', 'boat.png', color=yellow, heading=heading)
174 result += self._format_style('hsc', 'boat.png', color=pink, heading=heading)
175 result += self._format_style('hazarda', 'boat.png', color=red, heading=heading)
177 result += self._format_style('base-lost', 'boat-invis.png', color=white, heading=heading)
178 result += self._format_style('cargo-lost', 'boat-invis.png', color=green, heading=heading)
179 result += self._format_style('tanker-lost', 'boat-invis.png', color=yellow, heading=heading)
180 result += self._format_style('hsc-lost', 'boat-invis.png', color=pink, heading=heading)
181 result += self._format_style('hazarda-lost', 'boat-invis.png', color=red, heading=heading)
185 def get_style_name(self, nmea, is_lost):
187 Returns the name of the style based on nmea data
188 and whether the ship was seen recently or not.
190 if (nmea.strmmsi.startswith('00') and not nmea.strmmsi.startswith('000')):
193 if nmea.type in (41, 61, 71, 81): # major hazard materials
194 stylename = 'hazarda'
195 elif nmea.type >= 70 and nmea.type <= 79:
197 elif nmea.type >= 80 and nmea.type <= 89:
199 elif nmea.type >= 40 and nmea.type <= 49:
204 if (nmea.status in (1, 5, 6) and (nmea.sog == AIS_SOG_NOT_AVAILABLE or nmea.sog<0.5*AIS_SOG_SCALE)) \
205 or nmea.sog<1*AIS_SOG_SCALE:
211 if nmea.cog != AIS_COG_NOT_AVAILABLE:
212 course = int(nmea.cog/10.) # ais format correction
213 course = (course+5)//10*10 % 360 # go to neareast 10°
214 stylename += '-%d' % course
215 elif nmea.heading != AIS_NO_HEADING:
216 course = (nmea.heading+5)//10*10 % 360 # go to neareast 10°
217 stylename += '-%d' % course
220 STYLE = FishersStyle()
223 def format_boat_data(nmea, timeinfo=None, display_options=0):
225 timeinfo: None to generate a GoogleEarth 4 file, with no timeing information
226 True to generate a GoogleEarth 5 file, with start time from nmea
227 datetime or timestamp instance to generate a GoogleEarth 5 file, with start time from nmea and this enddate
230 timestamp_1, status, rot, sog, latitude, longitude, cog, heading, source_1 = Nmea1.to_values(nmea)
231 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)
233 if latitude == AIS_LAT_NOT_AVAILABLE or longitude == AIS_LON_NOT_AVAILABLE:
238 if timeinfo is not None and timeinfo != True:
239 if not isinstance(timeinfo, datetime):
240 timeinfo = datetime.utcfromtimestamp(timeinfo)
242 result += u'<Placemark>\n'
244 if not (display_options & KML_DISPLAYOPT_NONAMES):
245 result += u'<name>' + xml_escape(nmea.get_title()) + u'</name>\n'
247 result += u'<description><![CDATA[\n'
248 if display_options & KML_DISPLAYOPT_NONAMES:
249 result += u'Vessel name: ' + xml_escape(nmea.get_name()) + u'<br>\n'
251 dt_1 = datetime.utcfromtimestamp(timestamp_1)
252 if display_options & KML_DISPLAYOPT_HISTORICAL:
253 result += u'%s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
257 is_lost = dt_1 < datetime.utcnow()-LOST_PERIOD
259 result += u'Tack <b>lost</b> since %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
261 result += u'Last seen %s GMT<br>\n' % dt_1.strftime('%Y-%m-%d %H:%M:%S')
262 else: # timeinfo is not None
266 is_lost = timeinfo > dt_1 + LOST_PERIOD
268 if not mmsi.isdigit():
269 result += u'NO MMSI<br>\n'
270 is_land_station = False
272 result += u'MMSI: %s ' % mmsi
273 ref_mmsi = str(mmsi) # FIXME not needed
274 is_land_station = ref_mmsi.startswith('00') and not ref_mmsi.startswith('000')
276 ref_mmsi = ref_mmsi[2:]
277 result += u'('+COUNTRIES_MID.get(int(ref_mmsi[:3]), u'fake')+u')<br>\n'
278 if not is_land_station :
280 #result += u'IMO<a href="http://www.xvas.it/SPECIAL/VTship.php?imo=%(imo)s&mode=CK">%(imo)s</a><br>\n' % { 'imo': imo }
281 result += u'IMO: %s<br>\n' % imo
283 result += u'no known IMO<br>\n'
284 callsign = nmea.get_callsign(default=None)
285 if callsign is not None:
286 result += u'Callsign: %s<br>\n' % xml_escape(callsign)
288 result += u'Type: %s<br>\n' % SHIP_TYPES.get(type_, 'unknown')
289 if status != AIS_STATUS_NOT_AVAILABLE:
290 result += u'Status: %s<br>\n' % STATUS_CODES.get(status, 'unknown')
291 if cog != AIS_COG_NOT_AVAILABLE:
292 result += u'Course: %.01f°<br>\n' % (cog/10.)
293 if heading != AIS_NO_HEADING:
294 result += u'Heading: %d°<br>\n' % heading
295 if sog != AIS_SOG_NOT_AVAILABLE:
296 if sog != AIS_SOG_FAST_MOVER:
297 result += u'Speed: %.01f kts<br>\n' % (sog/AIS_SOG_SCALE)
299 result += u'Speed: more that than 102.2 kts<br>\n'
300 length = nmea.get_length()
301 width = nmea.get_width()
302 if length or width or draught:
303 result += u'Size: %dx%d' % (length, width)
305 result += u'/%.01f' % (draught/10.)
307 destination = nmea.get_destination(default=None)
309 result += u'Destination: %s<br>\n' % xml_escape(destination)
310 eta = nmea.get_eta_str(default=None)
312 result += u'ETA: %s<br>\n' % eta
314 if (display_options & KML_DISPLAYOPT_SOURCES) and (source_1 or source_5):
315 result += u'Source: '
317 result += Nmea.format_source(source_1)
318 if source_5 and source_1 != source_5:
319 result += u', '+ Nmea.format_source(source_5)
321 result += u'<a href="' + AIS_BASE_URL + u'/vessel/%(mmsi)s/">More...</a>' \
324 result += u'</description>\n'
326 result += u'<styleUrl>#%s</styleUrl>\n' \
327 % STYLE.get_style_name(nmea, is_lost)
329 result += u'<Point>\n'
330 result += u'<coordinates>%s,%s</coordinates>' \
331 % (longitude/AIS_LATLON_SCALE, latitude/AIS_LATLON_SCALE)
332 result += u'</Point>\n'
334 if timeinfo is not None:
335 #result += u'<TimeStamp><when>%s</when></TimeStamp>\n' \
336 # % (dt_1.strftime('%Y-%m-%dT%H:%M:%SZ'))
337 result += u'<gx:TimeSpan><begin>%s</begin>' \
338 % dt_1.strftime('%Y-%m-%dT%H:%M:%SZ')
340 result += u'<end>%s</end>' \
341 % timeinfo.strftime('%Y-%m-%dT%H:%M:%SZ')
342 result += u'</gx:TimeSpan>\n'
343 result += u'</Placemark>\n'
349 def format_fleet_lastpos(mmsi_iterator, document_name=None, display_options=0):
353 if document_name is None:
354 document_name = 'AIS database'
355 result += u'<name>%s</name>\n' % document_name
357 result += STYLE.make_header()
359 long_ago = datetime_to_timestamp(datetime.utcnow() - timedelta(90))
361 for mmsi in mmsi_iterator:
362 nmea = Nmea.new_from_lastinfo(mmsi)
363 if not (display_options & KML_DISPLAYOPT_SHOWHIDDEN) and strmmsi_to_mmsi(mmsi) in get_hidden_mmsi():
364 result += u'<Placemark>\n'
365 result += u'<name>' + xml_escape(nmea.get_title()) + u'</name>\n'
366 result += u'<description>Sorry, access to the position of that ship is restricted. It is not available for you.</description>\n'
367 result += u'</Placemark>\n'
369 if nmea.get_last_timestamp() < long_ago:
371 result += format_boat_data(nmea, display_options = display_options | KML_DISPLAYOPT_SOURCES)
376 def format_boat_intime_section(nmea_iterator, kml_displayopt=0):
379 for nmea in nmea_iterator:
380 if last_nmea is None:
383 timeinfo = datetime.utcfromtimestamp(last_nmea.timestamp_1)
385 result += format_boat_data(nmea, timeinfo,
386 kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
387 # make a copy because nmea will be patched with new data:
388 last_nmea = copy.copy(nmea)
390 result += u'<description>Vessel not found</description>'
394 def format_boat_intime(nmea_iterator):
397 result += STYLE.make_header()
398 result += format_boat_intime_section(nmea_iterator)
403 def format_boat_track_section(nmea_iterator, name=u''):
404 strcoordinates = '<Placemark>\n<LineString>\n<coordinates>\n'
406 for nmea in nmea_iterator:
408 name = nmea.get_title()
409 if nmea.longitude != AIS_LON_NOT_AVAILABLE and nmea.latitude != AIS_LAT_NOT_AVAILABLE:
410 if segment_length > 65000:
411 logging.debug('Line is too long. Spliting.')
412 strcoordinates += ' %.8f,%.8f' \
413 % (nmea.longitude/AIS_LATLON_SCALE,
414 nmea.latitude/AIS_LATLON_SCALE)
415 strcoordinates += '</coordinates>\n</LineString>\n</Placemark>\n<Placemark>\n<LineString>\n<coordinates>\n'
419 strcoordinates += ' %.8f,%.8f' \
420 % (nmea.longitude/AIS_LATLON_SCALE,
421 nmea.latitude/AIS_LATLON_SCALE)
422 strcoordinates += '</coordinates>\n</LineString></Placemark>\n'
425 result += u'<name>%s track</name>\n' % name
426 if len(strcoordinates)>39+2*(1+12+1+11)+42+1:
427 result += unicode(strcoordinates)
429 result += u'<description>No data available</description>\n'
433 def kml_to_kmz(inputstr):
434 if isinstance(inputstr, unicode):
435 inputstr = inputstr.encode('utf-8')
437 kmz = zipfile.ZipFile(output, 'w')
438 kmz.writestr('doc.kml', inputstr)
439 for iconname in STYLE.used_icons:
440 kmz.write('/usr/lib/ais/kmz_icons/'+iconname, iconname)
442 return output.getvalue()
447 from optparse import OptionParser, OptionGroup
449 parser = OptionParser(usage='%prog [options] { mmsi | @fleetname | #fleetid }+ | all')
451 parser.add_option('-d', '--debug',
452 action='store_true', dest='debug', default=False,
455 parser.add_option('-e', '--end',
456 action='store', dest='sdt_end', metavar="'YYYYMMDD HHMMSS'",
457 help='End data processing on that GMT date time.'
459 ' If a date is provided without time, time defaults to 235959.')
460 parser.add_option('-s', '--start',
461 action='store', dest='sdt_start', metavar="'YYYYMMDD HHMMSS'",
462 help='Start data processing on that date.'
463 ' Using that option enables multiple output of the same boat.'
464 ' Disabled by default.'
465 ' If a date is provided without time, time default to 000000.'
466 ' If other options enable multiple output, default to 1 day before'
468 parser.add_option('--duration',
469 action='store', dest='sdt_duration', metavar="DURATION",
470 help='Duration of reference period.'
471 ' Last character may be S for seconds, M(inutes), D(ays), W(eeks)'
472 ' Default is seconds.'
473 ' This is the time length bewteen --start and --end above.'
474 ' If you want multiple output of the same boat, you may use '
475 ' --start, --end or --duration, 2 of them, but not 3 of them.')
476 parser.add_option('-g', '--granularity',
477 action='store', type='int', dest='granularity', metavar='SECONDS',
478 help='Dump only one position every granularity seconds.'
479 'Using that option enables multiple output of the same boat.'
480 'If other options enable multiple output, defaults to 600'
482 parser.add_option('--max',
483 action='store', type='int', dest='max_count', metavar='NUMBER',
484 help='Dump a maximum of NUMBER positions every granularity seconds.'
485 'Using that option enables multiple output of the same boat.')
487 parser.add_option('--show-hidden-ships',
488 action='store_true', dest='show_hidden_ships', default=False,
489 help='Include hidden ships in results')
491 parser.add_option('--filter-knownposition',
492 action='store_true', dest='filter_knownposition', default=False,
493 help="eliminate unknown positions from results.")
495 parser.add_option('--filter-speedcheck',
496 action='store', type='int', dest='speedcheck', default=200, metavar='KNOTS',
497 help='Eliminate erroneaous positions from results,'
498 ' based on impossible speed.')
500 parser.add_option('--filter-type',
501 action='append', type='int', dest='type_list', metavar="TYPE",
502 help="process a specific ship type.")
503 parser.add_option('--help-types',
504 action='store_true', dest='help_types', default=False,
505 help="display list of available types")
507 parser.add_option('--filter-area',
508 action='store', type='str', dest='area_file', metavar="FILE.KML",
509 help="only process a specific area as defined in a kml polygon file.")
510 parser.add_option('--filter-farfrom',
511 action='store', dest='far_from', nargs=3, metavar='LAT LONG MILES',
512 help="only show ships farther than MILES miles from LAT,LONG")
513 parser.add_option('--filter-closeto',
514 action='store', dest='close_to', nargs=3, metavar='LAT LONG MILES',
515 help="only show ships closer than MILES miles from LAT,LONG")
516 parser.add_option('--filter-sog-le',
517 action='store', dest='sog_le', metavar='KNOTS',
518 help='only show ships when speed over ground is less or equal to KNOTS.')
521 parser.add_option('--format',
522 choices=('positions', 'track', 'animation'), dest='format', default='positions',
523 help="select output format: positions(*) or track or animation")
525 parser.add_option('--kml',
526 action='store_true', dest='output_kml', default=False,
527 help="Output a kml file. Default is output of a kmz file with icons")
528 parser.add_option('--inner-kml',
529 action='store_true', dest='output_innerkml', default=False,
530 help='Output a kml fragment file without the <Document> wrappers.\n'
531 'File should be reproccessed to be usefull. That option implies --kml')
533 parser.add_option('--style',
534 choices=('fishers', 'pelagos'), dest='style', default='fishers',
535 help='select one of the predefined style for display: '
536 'fishers(*) or pelagos')
538 parser.add_option('--icon-size',
539 action='store', type='float', dest='icon_size', metavar='SCALE', default=0.5,
540 help='Set icons size. 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()
565 if options.help_types:
566 keys = SHIP_TYPES.keys()
569 print k, SHIP_TYPES[k]
575 loglevel = logging.DEBUG
577 loglevel = logging.INFO
578 logging.basicConfig(level=loglevel, format='%(asctime)s %(levelname)s %(message)s')
585 print >> sys.stderr, "No ship to process"
588 target_mmsi_iterator = []
593 elif arg.startswith('@'):
594 target_mmsi_iterator += load_fleet_to_uset(fleetname_to_fleetid(arg[1:]))
595 elif arg.startswith('^'):
596 target_mmsi_iterator += load_fleet_to_uset(int(arg[1:]))
598 target_mmsi_iterator.append(arg)
600 if target_mmsi_iterator:
601 logging.warning('Selecting all ships, ignoring other arguments')
602 target_mmsi_iterator = all_mmsi_generator()
604 if not options.show_hidden_ships:
605 target_mmsi_iterator = mmsiiterator_nohiddenship(target_mmsi_iterator)
611 if options.sdt_start:
612 # remove non digit characters
613 options.sdt_start = "".join([ c for c in options.sdt_start if c.isdigit()])
614 if len(options.sdt_start)==14:
615 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d%H%M%S')
616 elif len(options.sdt_start)==8:
617 options.sdt_start = datetime.strptime(options.sdt_start, '%Y%m%d')
619 print >> sys.stderr, "Invalid format for --start option"
623 # remove non digit characters
624 options.sdt_end = "".join([ c for c in options.sdt_end if c.isdigit()])
625 if len(options.sdt_end)==14:
626 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d%H%M%S')
627 elif len(options.sdt_end)==8:
628 options.sdt_end = datetime.strptime(options.sdt_end, '%Y%m%d')
629 options.sdt_end = datetime.combine(options.sdt_end.date(), time(23, 59, 59))
631 print >> sys.stderr, "Invalid format for --end option"
634 if options.sdt_duration:
636 options.sdt_duration = options.sdt_duration.replace(' ', '')
638 options.sdt_duration = options.sdt_duration.upper()
639 if options.sdt_duration[-1] == 'S':
640 options.sdt_duration = options.sdt_duration[:-1]
642 elif options.sdt_duration[-1] == 'M':
643 options.sdt_duration = options.sdt_duration[:-1]
645 elif options.sdt_duration[-1] == 'H':
646 options.sdt_duration = options.sdt_duration[:-1]
647 duration_unit = 60*60
648 elif options.sdt_duration[-1] == 'D':
649 options.sdt_duration = options.sdt_duration[:-1]
650 duration_unit = 24*60*60
651 elif options.sdt_duration[-1] == 'W':
652 options.sdt_duration = options.sdt_duration[:-1]
653 duration_unit = 7*24*60*60
657 options.sdt_duration = long(options.sdt_duration)
659 print >> sys.stderr, "Can't parse duration"
661 options.sdt_duration = timedelta(0, options.sdt_duration * duration_unit)
663 if options.sdt_start or options.sdt_duration or options.granularity is not None or options.max_count:
664 # Time period is enabled (note that date_end only defaults to one day archives ending then)
665 if not options.sdt_start and not options.sdt_end and not options.sdt_duration:
666 options.sdt_duration = timedelta(1) # One day
667 # continue without else
668 if not options.sdt_start and not options.sdt_end and options.sdt_duration:
669 dt_end = datetime.utcnow()
670 dt_start = dt_end - options.sdt_duration
671 #elif not options.sdt_start and options.sdt_end and not options.sdt_duration:
673 elif not options.sdt_start and options.sdt_end and options.sdt_duration:
674 dt_end = options.sdt_end
675 dt_start = dt_end - options.sdt_duration
676 elif options.sdt_start and not options.sdt_end and not options.sdt_duration:
677 dt_start = options.sdt_start
678 dt_end = datetime.utcnow()
679 elif options.sdt_start and not options.sdt_end and options.sdt_duration:
680 dt_start = options.sdt_start
681 dt_end = dt_start + options.sdt_duration
682 elif options.sdt_start and options.sdt_end and not options.sdt_duration:
683 dt_start = options.sdt_start
684 dt_end = options.sdt_end
686 assert options.sdt_start and options.sdt_end and options.sdt_duration, 'Internal error'
687 print >> sys.stderr, "You can't have all 3 --start --end and --duration"
689 if options.granularity is None:
690 options.granularity = 600
692 # Only get one position
695 dt_end = options.sdt_end
697 dt_end = datetime.utcnow()
698 options.max_count = 1
699 if options.granularity is None:
700 options.granularity = 600
702 logging.debug('--start is %s', dt_start)
703 logging.debug('--end is %s', dt_end)
711 if options.filter_knownposition:
712 filters.append(filter_knownposition)
714 if options.speedcheck != 0:
715 maxmps = options.speedcheck / 3600. # from knots to NM per seconds
716 filters.append(lambda nmea: filter_speedcheck(nmea, maxmps))
718 if options.area_file:
719 area = load_area_from_kml_polygon(options.area_file)
720 filters.append(lambda nmea: filter_area(nmea, area))
724 lat = clean_latitude(unicode(options.close_to[0], 'utf-8'))
725 lon = clean_longitude(unicode(options.close_to[1], 'utf-8'))
726 except LatLonFormatError as err:
727 print >> sys.stderr, err.args
729 miles = float(options.close_to[2])
730 filters.append(lambda nmea: filter_close_to(nmea, lat, lon, miles))
734 lat = clean_latitude(unicode(options.far_from[0], 'utf-8'))
735 lon = clean_longitude(unicode(options.far_from[1], 'utf-8'))
736 except LatLonFormatError as err:
737 print >> sys.stderr, err.args
739 miles = float(options.far_from[2])
740 filters.append(lambda nmea: filter_far_from(nmea, lat, lon, miles))
743 filters.append(lambda nmea: filter_sog_le(nmea, float(options.sog_le)))
745 if options.type_list:
746 def filter_type(nmea):
747 #print nmea.type, repr(options.type_list), nmea.type in options.type_list
748 #print repr(nmea.get_dump_row())
749 return nmea.type in options.type_list
750 filters.append(filter_type)
756 if options.style == 'pelagos':
757 STYLE = PelagosStyle()
759 kml_displayopt = options.kml_displayopt_noname | options.kml_displayopt_sources
761 STYLE.icon_size = options.icon_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'
789 result += format_boat_intime_section(nmea_generator, kml_displayopt|KML_DISPLAYOPT_HISTORICAL)
790 result += '</Folder>\n'
791 if not options.output_innerkml:
794 elif options.format=='track':
796 if not options.output_innerkml:
798 # don't call STYLE.make_header since there is no icons
799 for mmsi in target_mmsi_iterator:
800 nmea_generator = NmeaFeeder(mmsi, dt_end, dt_start, filters, granularity=options.granularity, max_count=options.max_count)
801 result += '<Folder>\n'
802 result += format_boat_track_section(nmea_generator)
803 result += '</Folder>\n'
804 if not options.output_innerkml:
808 print >> sys.stderr, 'Unknown output format'
811 result = result.encode('utf-8')
813 if not options.output_kml:
814 result = kml_to_kmz(result)
818 if __name__ == '__main__':