|
Module imo_001_22_area_notice
|
|
1
2 __author__ = 'Kurt Schwehr'
3 __version__ = '$Revision: 4799 $'.split()[1]
4 __revision__ = __version__
5 __date__ = '$Date: 2006-09-25 11:09:02 -0400 (Mon, 25 Sep 2006) $'.split()[1]
6 __copyright__ = '2009'
7 __license__ = 'GPL v3'
8 __contact__ = 'kurt at ccom.unh.edu'
9
10
11
12 __doc__ ='''
13 Trying to do a more sane design for AIS BBM message
14
15 @requires: U{Python<http://python.org/>} >= 2.6
16 @requires: U{epydoc<http://epydoc.sourceforge.net/>} >= 3.0.1
17 @requires: U{lxml<http://codespeak.net/lxml/lxmlhtml.html>} >= 2.0
18 @requires: U{shapely<http://pypi.python.org/pypi/Shapely/>}
19 @requires: U{BitVector<http://pypi.python.org/pypi/BitVector/>}
20 @requires: U{pyproj<http://code.google.com/p/pyproj/>}
21 @requires: U{geojson<http://pypi.python.org/pypi/geojson/>}
22
23 @license: GPL v3
24 @undocumented: __doc__
25 @since: 2009-Jun-01
26 @status: under development
27 @organization: U{CCOM<http://ccom.unh.edu/>}
28
29 @todo: handle polyline and polygons that span multiple subareas
30 @todo: handle text that spans adjacent subareas
31 '''
32
33
34
35 import sys
36
37 import datetime
38 from operator import xor
39
40 import operator
41
42 import math
43
44 from pyproj import Proj
45 import shapely.geometry
46
47
48 import lxml
49 from lxml.html import builder as E
50
51 from BitVector import BitVector
52
53 import binary, aisstring
54
55
56 kml_head = '''<?xml version="1.0" encoding="UTF-8"?>
57 <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
58 <Document>
59 '''
60 'Beginning of a KML file for visualization'
61
62 kml_tail = '''</Document>
63 </kml>
64 '''
65 'Finish a kml file'
68 return int(( lon + 180 ) / 6) + 1
69
70 iso8601_timeformat = '%Y-%m-%dT%H:%M:%SZ'
71 '''ISO time format for NetworkLinkControl strftime
72 @see: U{KML Tutorial<http://code.google.com/apis/kml/documentation/kml_21tutorial.html#updates>}
73 '''
74
75
76 nmea_talkers = {
77 'AG':'Autopilot - General',
78 'AI':'Automatic Identification System',
79 'AP':'Autopilot - Magnetic',
80 'CC':'Computer - Programmed Calculator (outdated)',
81 'CD':'Communications - Digital Selective Calling (DSC)',
82 'CM':'Computer - Memory Data (outdated)',
83 'CS':'Communications - Satellite',
84 'CT':'Communications - Radio-Telephone (MF/HF)',
85 'CV':'Communications - Radio-Telephone (VHF)',
86 'CX':'Communications - Scanning Receiver',
87 'DE':'DECCA Navigation (outdated)',
88 'DF':'Direction Finder',
89 'EC':'Electronic Chart Display & Information System (ECDIS)',
90 'EP':'Emergency Position Indicating Beacon (EPIRB)',
91 'ER':'Engine Room Monitoring Systems',
92 'GP':'Global Positioning System (GPS)',
93 'HC':'Heading - Magnetic Compass',
94 'HE':'Heading - North Seeking Gyro',
95 'HN':'Heading - Non North Seeking Gyro',
96 'II':'Integrated Instrumentation',
97 'IN':'Integrated Navigation',
98 'LA':'Loran A (outdated)',
99 'LC':'Loran C',
100 'MP':'Microwave Positioning System (outdated)',
101 'OM':'OMEGA Navigation System (outdated)',
102 'OS':'Distress Alarm System (outdated)',
103 'RA':'RADAR and/or ARPA',
104 'SD':'Sounder, Depth',
105 'SN':'Electronic Positioning System, other/general',
106 'SS':'Sounder, Scanning',
107 'TI':'Turn Rate Indicator',
108 'TR':'TRANSIT Navigation System',
109 'VD':'Velocity Sensor, Doppler, other/general',
110 'DM':'Velocity Sensor, Speed Log, Water, Magnetic',
111 'VW':'Velocity Sensor, Speed Log, Water, Mechanical',
112 'WI':'Weather Instruments ',
113 'YC':'Transducer - Temperature (outdated)',
114 'YD':'Transducer - Displacement, Angular or Linear (outdated)',
115 'YF':'Transducer - Frequency (outdated)',
116 'YL':'Transducer - Level (outdated)',
117 'YP':'Transducer - Pressure (outdated)',
118 'YR':'Transducer - Flow Rate (outdated)',
119 'YT':'Transducer - Tachometer (outdated)',
120 'YV':'Transducer - Volume (outdated)',
121 'YX':'Transducer',
122 'ZA':'Timekeeper - Atomic Clock',
123 'ZC':'Timekeeper - Chronometer',
124 'ZQ':'Timekeeper - Quartz',
125 'ZV':'Timekeeper - Radio Update, WWV or WWVH',
126 }
127 '''Prefixes for NMEA strings that say where a message originated. http://gpsd.berlios.de/NMEA.txt
128 BBM messages may require having EC as the prefix.
129 '''
130
131 notice_type = {
132 'cau_mammals_not_obs': 0,
133 'cau_mammals_reduce_speed': 1,
134 'cau_mammals_stay_clear': 2,
135 'cau_mammals_report_sightings': 3,
136 'cau_habitat_reduce_speed': 4,
137 'cau_habitat_stay_clear': 5,
138 'cau_habitat_no_fishing_or_anchoring': 6,
139 'cau_congestion': 8,
140 'cau_event': 9,
141 'cau_divers': 10,
142 'cau_swimmers': 11,
143 'cau_dredging': 12,
144 'cau_surveying': 13,
145 'cau_underwater_ops': 14,
146 'cau_seaplane_ops': 15,
147 'cau_nets_in_water': 16,
148 'cau_cluster_fishing_vessels': 17,
149 'cau_fairway_closed': 18,
150 'cau_harbor_closed': 19,
151 'cau_risk_see_text': 20,
152 'cau_auv_ops': 21,
153 'env_storm_front': 23,
154 'env_ice': 24,
155 'env_storm': 25,
156 'env_wind': 26,
157 'env_waves': 27,
158 'env_restr_vis': 28,
159 'env_currents': 29,
160 'env_icing': 30,
161 'res_no_fishing': 32,
162 'res_no_anchoring': 33,
163 'res_entry_approval_req': 34,
164 'res_no_entry': 35,
165 'res_military_ops': 36,
166 'res_firing_danger': 37,
167 'anc_open': 40,
168 'anc_closed': 41,
169 'anc_prohibited': 42,
170 'anc_deep_draft': 43,
171 'anc_Shallow': 44,
172 'anc_transfer': 45,
173 'sec_1': 56,
174 'sec_2': 57,
175 'sec_3': 58,
176 'dis_adrift': 64,
177 'dis_sinking': 65,
178 'dis_abandoning': 66,
179 'dis_requ_medical': 67,
180 'dis_flooding': 68,
181 'dis_fire_explosion': 69,
182 'dis_grounding': 70,
183 'dis_collision': 71,
184 'dis_listing_capsizing': 72,
185 'dis_under_assault': 73,
186 'dis_person_overboard': 74,
187 'dis_sar': 75,
188 'dis_pollution': 76,
189 'inst_contact_vts_here': 80,
190 'inst_contact_port_admin_here': 81,
191 'inst_do_not_proceed_beyond_here': 82,
192 'inst_await_instr_here': 83,
193 'info_pilot_boarding': 88,
194 'info_icebreaker_staging': 89,
195 'info_refuge': 90,
196 'info_pos_icebreakers': 91,
197 'info_pos_response_units': 92,
198 'chart_sunken_vessel': 96,
199 'chart_Submerged_obj': 97,
200 'chart_Semi_submerged_obj': 98,
201 'chart_shoal': 99,
202 'chart_shoal_due_North': 100,
203 'chart_Shoal_due_North': 100,
204 'chart_Shoal_due_East': 101,
205 'chart_Shoal_due_South': 102,
206 'chart_Shoal_due_West': 103,
207 'chart_channel_obstruction': 104,
208 'chart_reduced_vert_clearance': 105,
209 'chart_bridge_closed': 106,
210 'chart_bridge_part_open': 107,
211 'chart_bridge_fully_open': 108,
212 'report_of_icing': 112,
213 'report_of_see_text': 114,
214 'other_see_text': 125,
215 'cancel_area_notice': 126,
216 'undefined': 127,
217 0: 'Caution Area: Marine mammals NOT observed',
218 1: 'Caution Area: Marine mammals in area - Reduce Speed',
219 2: 'Caution Area: Marine mammals in area - Stay Clear',
220 3: 'Caution Area: Marine mammals in area - Report Sightings',
221 4: 'Caution Area: Protected Habitat - Reduce Speed',
222 5: 'Caution Area: Protected Habitat - Stay Clear',
223 6: 'Caution Area: Protected Habitat - No fishing or anchoring',
224 7: 'Reserved',
225 8: 'Caution Area: Traffic congestion',
226 9: 'Caution Area: Marine event',
227 10: 'Caution Area: Divers down',
228 11: 'Caution Area: Swim area',
229 12: 'Caution Area: Dredge operations',
230 13: 'Caution Area: Survey operations',
231 14: 'Caution Area: Underwater operation',
232 15: 'Caution Area: Seaplane operations',
233 16: 'Caution Area: Fishery - nets in water',
234 17: 'Caution Area: Cluster of fishing vessels',
235 18: 'Caution Area: Fairway closed',
236 19: 'Caution Area: Harbor closed',
237 20: 'Caution Area: Risk - define in free text field',
238 21: 'Caution Area: Underwater vehicle operation',
239 22: 'Reserved',
240 23: 'Storm front (line squall)',
241 24: 'Env. Caution Area: Hazardous sea ice',
242 25: 'Env. Caution Area: Storm warning (storm cell or line of storms)',
243 26: 'Env. Caution Area: High wind',
244 27: 'Env. Caution Area: High waves',
245 28: 'Env. Caution Area: Restricted visibility (fog, rain, etc)',
246 29: 'Env. Caution Area: Strong currents',
247 30: 'Env. Caution Area: Heavy icing',
248 31: 'Reserved',
249 32: 'Restricted Area: Fishing prohibited',
250 33: 'Restricted Area: No anchoring.',
251 34: 'Restricted Area: Entry approval required prior to transit',
252 35: 'Restricted Area: Entry prohibited',
253 36: 'Restricted Area: Active military OPAREA',
254 37: 'Restricted Area: Firing - danger area.',
255 38: 'Reserved',
256 39: 'Reserved',
257 40: 'Anchorage Area: Anchorage open',
258 41: 'Anchorage Area: Anchorage closed',
259 42: 'Anchorage Area: Anchoring prohibited',
260 43: 'Anchorage Area: Deep draft anchorage',
261 44: 'Anchorage Area: Shallow draft anchorage',
262 45: 'Anchorage Area: Vessel transfer operations',
263 46: 'Reserved',
264 47: 'Reserved',
265 48: 'Reserved',
266 49: 'Reserved',
267 50: 'Reserved',
268 51: 'Reserved',
269 52: 'Reserved',
270 53: 'Reserved',
271 54: 'Reserved',
272 55: 'Reserved',
273 56: 'Security Alert - Level 1',
274 57: 'Security Alert - Level 2',
275 58: 'Security Alert - Level 3',
276 59: 'Reserved',
277 60: 'Reserved',
278 61: 'Reserved',
279 62: 'Reserved',
280 63: 'Reserved',
281 64: 'Distress Area: Vessel disabled and adrift',
282 65: 'Distress Area: Vessel sinking',
283 66: 'Distress Area: Vessel abandoning ship',
284 67: 'Distress Area: Vessel requests medical assistance',
285 68: 'Distress Area: Vessel flooding',
286 69: 'Distress Area: Vessel fire/explosion',
287 70: 'Distress Area: Vessel grounding',
288 71: 'Distress Area: Vessel collision',
289 72: 'Distress Area: Vessel listing/capsizing',
290 73: 'Distress Area: Vessel under assault',
291 74: 'Distress Area: Person overboard',
292 75: 'Distress Area: SAR area',
293 76: 'Distress Area: Pollution response area',
294 77: 'Reserved',
295 78: 'Reserved',
296 79: 'Reserved',
297 80: 'Instruction: Contact VTS at this point/juncture',
298 81: 'Instruction: Contact Port Administration at this point/juncture',
299 82: 'Instruction: Do not proceed beyond this point/juncture',
300 83: 'Instruction: Await instructions prior to proceeding beyond this point/juncture',
301 84: 'Reserved',
302 85: 'Reserved',
303 86: 'Reserved',
304 87: 'Reserved',
305 88: 'Information: Pilot boarding position',
306 89: 'Information: Icebreaker waiting area',
307 90: 'Information: Places of refuge',
308 91: 'Information: Position of icebreakers',
309 92: 'Information: Location of response units',
310 93: 'Reserved',
311 94: 'Reserved',
312 95: 'Reserved',
313 96: 'Chart Feature: Sunken vessel',
314 97: 'Chart Feature: Submerged object',
315 98: 'Chart Feature: Semi-submerged object',
316 99: 'Chart Feature: Shoal area',
317 100: 'Chart Feature: Shoal area due North',
318 101: 'Chart Feature: Shoal area due East',
319 102: 'Chart Feature: Shoal area due South',
320 103: 'Chart Feature: Shoal area due West',
321 104: 'Chart Feature: Channel obstruction',
322 105: 'Chart Feature: Reduced vertical clearance',
323 106: 'Chart Feature: Bridge closed',
324 107: 'Chart Feature: Bridge partially open',
325 108: 'Chart Feature: Bridge fully open',
326 109: 'Reserved',
327 110: 'Reserved',
328 111: 'Reserved',
329 112: 'Report from ship: Icing info',
330 113: 'Reserved',
331 114: 'Report from ship: Miscellaneous information - define in free text field',
332 115: 'Reserved',
333 116: 'Reserved',
334 117: 'Reserved',
335 118: 'Reserved',
336 119: 'Reserved',
337 120: 'Reserved',
338 121: 'Reserved',
339 122: 'Reserved',
340 123: 'Reserved',
341 124: 'Reserved',
342 125: 'Other - Define in free text field',
343 126: 'Cancellation - cancel area as identified by Message Linka',
344 127: 'Undefined (default)',
345 }
346 ''' by name or number.
347
348 cau == caution area
349 res == restricted
350 anc == anchorage
351 env == environmental caution
352 sec == security
353 des == distress
354 inst == instructional
355 info == informational
356 chart == chart features'''
359 d = {}
360 for k,v in notice_type.iteritems():
361 if isinstance (k,str):
362 d[v] = k
363 return d
364
365 short_notice = _make_short_notice()
366
367 -def frange(start, stop=None, step=None):
368 'range but with float steps'
369 if stop is None:
370 stop = float(start)
371 start = 0.0
372 if step is None:
373 step = 1.0
374 cur = float(start)
375 while cur < stop:
376 yield cur
377 cur += step
378
381 return map(operator.add,a,b)
382
384 'counter clockwise rotation by theta radians'
385 x,y = a
386 x1 = x * math.cos(theta) - y * math.sin(theta)
387 y1 = x * math.sin(theta) + y * math.cos(theta)
388 return x1,y1
389
391 return (degrees / 180.) * math.pi
393 return (radians / math.pi) * 180.
394
396 '''Convert a geointerface geometry to KML
397
398 @param geom_dict: Dictionary containing 'geometry' as defined by the geo interface / geojson / shapely
399 '''
400 geom_type = geom_dict['geometry']['type']
401 geom_coords = geom_dict['geometry']['coordinates']
402
403 if geom_type == 'Point':
404 return '<Point><coordinates>{lon},{lat},0</coordinates></Point>'.format(lon = geom_coords[0], lat = geom_coords[1])
405 elif geom_type == 'Polygon':
406 o = ['<Polygon><outerBoundaryIs><LinearRing><coordinates>']
407 for pt in geom_coords:
408 o.append('\t%f,%f,0' % (pt[0],pt[1]))
409 o.append('</coordinates></LinearRing></outerBoundaryIs></Polygon>')
410 return '\n'.join(o)
411
412 elif geom_type == 'LineString':
413 o = ['<LineString><coordinates>']
414 for pt in geom_coords:
415 o.append('\t%f,%f,0' % (pt[0],pt[1]))
416 o.append('</coordinates></LineString>')
417 return '\n'.join(o)
418
419 raise ValueError('Not a recognized __geo_interface__ type: %s' % (geom_type))
420
425
428 self.fieldname = fieldname
429 self.value = value
431 return "Validation on %s failed (value %s) while packing" % (self.fieldname, self.value)
432
435 self.fieldname = fieldname
436 self.value = value
438 return "Validation on %s failed (value %s) while unpacking" % (self.fieldname, self.value)
439
443 nmea = map(ord, sentence.split('*')[0])
444 checksum = reduce(xor, nmea)
445
446 return hex(checksum).split('x')[1].upper()
447
449 '''AIS VDM Object for AIS top level messages 1 through 64.
450
451 Class attribute payload_bits must be set by the child class.
452 '''
453 - def __init__(self, message_id = None, repeat_indicator = None, source_mmsi = None):
454 self.message_id = message_id
455 self.repeat_indicator = repeat_indicator
456 self.source_mmsi = source_mmsi
457
459 '''Child classes must implement this. Return a BitVector
460 representation. Child classes do NOT include the Message ID, repeat indicator, or source mmsi'''
461 raise NotImplementedError()
462
464 if message_id is None: message_id = self.message_id
465 if repeat_indicator is None: repeat_indicator = self.repeat_indicator
466 if source_mmsi is None: source_mmsi = self.source_mmsi
467
468 if message_id is None or message_id<1 or message_id>63:
469 raise AisPackingException('message_id must be valid',message_id)
470 if repeat_indicator is None or repeat_indicator<0 or repeat_indicator>3:
471 raise AisPackingException('repeat_indicator must be valid',repeat_indicator)
472
473
474 bvList = []
475 bvList.append(binary.setBitVectorSize(BitVector(intVal=message_id),6))
476 bvList.append(binary.setBitVectorSize(BitVector(intVal=repeat_indicator),2))
477 bvList.append(binary.setBitVectorSize(BitVector(intVal=source_mmsi),30))
478 bv = binary.joinBV(bvList)
479 if len(bv) != 38:
480 raise AisPackingExpeption('invalid header size',len(bv))
481 return bv
482
483
484
485
486
487
488 - def get_aivdm(self, sequence_num = None, channel = 'A', normal_form=False, source_mmsi=None, repeat_indicator=None):
489 '''return the nmea string as if it had been received. Assumes that payload_bits has already been set
490 @param sequence_num: Which channel of AIVDM on the local serial line (in 0..9)
491 @param channel: VHF radio channel ("A" or "B")
492 @param normal_form: Set to true to always return aone line NMEA message. False allows multi-sentence messages
493 @return: AIVDM sentences
494 @rtype: list (even for normal_form for consistency)
495 '''
496 if sequence_num is not None and (sequence_num <= 0 or sequence_num >= 9):
497 raise AisPackingException('sequence_num',sequence_num)
498 if channel not in ('A','B'):
499 raise AisPackingException('channel',channel)
500
501 if repeat_indicator is None:
502 try:
503 repeat_indicator = self.repeat_indicator
504 except:
505 repeat_indicator = 0
506
507 if source_mmsi is None:
508 try:
509 source_mmsi = self.source_mmsi
510 except:
511 raise AisPackingException('source_mmsi',source_mmsi)
512
513 header = self.get_bits_header(repeat_indicator=repeat_indicator,source_mmsi=source_mmsi)
514 payload, pad = binary.bitvectoais6(header + self.get_bits())
515
516 if sequence_num is None:
517 sequence_num = ''
518
519 if normal_form:
520
521 sentence = '!AIVDM,{tot_sentences},{sentence_num},{sequence_num},{channel},{payload},{pad}'.format(
522 tot_sentences=1, sentence_num=1,
523 sequence_num=sequence_num, channel=channel,
524 payload=payload, pad=pad
525 )
526 return [sentence + '*' + nmea_checksum_hex(sentence),]
527
528 max_payload_char = 43
529
530 sentences = []
531 tot_sentences = 1 + len(payload) / max_payload_char
532 sentence_num = 0
533 for i in range(tot_sentences-1):
534 sentence_num = i+1
535 payload_part = payload[i*max_payload_char:(i+1)*max_payload_char]
536 sentence = '!AIVDM,{tot_sentences},{sentence_num},{sequence_num},{channel},{payload},{pad}'.format(
537 tot_sentences=tot_sentences, sentence_num=sentence_num,
538 sequence_num=sequence_num, channel=channel,
539 payload=payload_part, pad=0
540 )
541 sentences.append(sentence + '*' + nmea_checksum_hex(sentence))
542
543 sentence_num += 1
544 payload_part = payload[(sentence_num-1)*max_payload_char:]
545 sentence = '!AIVDM,{tot_sentences},{sentence_num},{sequence_num},{channel},{payload},{pad}'.format(
546 tot_sentences=tot_sentences, sentence_num=sentence_num,
547 sequence_num=sequence_num, channel=channel,
548 payload=payload_part, pad=pad
549 )
550 sentences.append(sentence + '*' + nmea_checksum_hex(sentence))
551
552 return sentences
553
554 - def kml(self,with_style=False,full=False,with_time=False):
555 '''return kml str for google earth
556 @param with_style: if style is True, it will use the standard style. Set to a name for a custom style
557 @param with_time: enable timestamps in Google Earth
558 '''
559 o = []
560 if full:
561 o.append(kml_head)
562 o.append(file('areanotice_styles.kml').read())
563 html = self.html()
564 for area in self.areas:
565 geo_i = area.__geo_interface__
566 if 'geometry' not in geo_i:
567
568 continue
569 kml_shape = geom2kml(geo_i)
570
571 o.append('<Placemark>')
572 try:
573 o.append('<name>%s</name>' % (self.name))
574 except:
575 o.append('<name>%s</name>' % (short_notice[self.area_type].replace('_',' '),))
576 if with_style:
577 if isinstance(with_style,str):
578 o.append('<styleUrl>%s</styleUrl>'% (with_style,))
579 o.append('<styleUrl>#AreaNotice_%d</styleUrl>' % self.area_type)
580 o.append('<description>')
581 o.append('<i>AreaNotice - %s</i>' % (notice_type[self.area_type],) )
582 o.append(html)
583 o.append('</description>')
584
585 o.append(kml_shape)
586 if with_time:
587 start = datetime.datetime.strftime(self.when,iso8601_timeformat)
588 end = datetime.datetime.strftime(self.when + datetime.timedelta(minutes=self.duration),iso8601_timeformat)
589 o.append('''<TimeSpan><begin>%s</begin><end>%s</end></TimeSpan>''' % (start,end))
590
591 o.append('</Placemark>\n')
592
593 if full:
594 o.append(kml_tail)
595
596 return '\n'.join(o)
597
598
599 -class BBM (AIVDM):
600 ''' Binary Broadcast Message - MessageID 8. Generically support messages of this type
601 BBM defined in 80_330e_PAS - IEC/PAS 61162-100 Ed.1
602
603 Maritime navigation and radiocommunication equipment and systems -
604 Digital interfaces - Part 100: Singlto IEC 61162-1 for the UAIS
605
606 NMEA BBM can be 8, 19, or 21. It can also handle the text message 14.
607 '''
608 max_payload_char = 41
609 'Maximum length of characters that can go inside the BBM payload'
610
612 assert message_id in (8, 19, 21)
613 self.message_id = message_id
614
615 - def get_bbm(self, talker='EC', sequence_num = None, channel = 'A'):
616 if not isinstance(talker,str) or len(talker) != 2:
617 AisPackingException('talker',talker)
618 if sequence_num is not None and (sequence_num <= 0 or sequence_num >= 9):
619 raise AisPackingException('sequence_num',sequence_num)
620 if channel not in ('A','B'):
621 raise AisPackingException('channel',channel)
622
623 if sequence_num is None:
624 sequence_num = 3
625
626 payload, pad = binary.bitvectoais6(self.get_bits())
627
628 sentences = []
629 tot_sentences = 1 + len(payload) / self.max_payload_char
630 sentence_num = 0
631 for i in range(tot_sentences-1):
632 sentence_num = i+1
633 payload_part = payload[i*self.max_payload_char:(i+1)*self.max_payload_char]
634 sentence = '!{talker}BBM,{tot_sentences},{sentence_num},{sequence_num},{channel},{msg_type},{payload},{pad}'.format(
635 talker=talker,
636 tot_sentences=tot_sentences, sentence_num=sentence_num,
637 sequence_num=sequence_num, channel=channel,
638 msg_type=self.message_id,
639 payload=payload_part, pad=0
640 )
641 sentences.append(sentence + '*' + nmea_checksum_hex(sentence))
642
643 sentence_num += 1
644 payload_part = payload[(sentence_num-1)*self.max_payload_char:]
645 sentence = '!{talker}BBM,{tot_sentences},{sentence_num},{sequence_num},{channel},{msg_type},{payload},{pad}'.format(
646 talker=talker,
647 tot_sentences=tot_sentences, sentence_num=sentence_num,
648 sequence_num=sequence_num, channel=channel,
649 msg_type=self.message_id,
650 payload=payload_part, pad=pad
651 )
652 sentences.append(sentence + '*' + nmea_checksum_hex(sentence))
653
654 return sentences
655
660
663 area_shape = 0
664 - def __init__(self, lon=None, lat=None, radius=0, bits=None):
665 '''@param radius: 0 is a point, otherwise less than or equal to 409500m. Scale factor is automatic
666 @param bits: string of 1's and 0's or a BitVector
667 '''
668 if lon is not None:
669 assert lon >= -180. and lon <= 180.
670 self.lon = lon
671 assert lat >= -90. and lat <= 90.
672 self.lat = lat
673
674 assert radius >= 0 and radius < 409500
675 self.radius = radius
676
677 if radius / 100. >= 4095: self.scale_factor_raw = 3
678 elif radius / 10. > 4095: self.scale_factor_raw = 2
679 elif radius > 4095: self.scale_factor_raw = 1
680 else: self.scale_factor_raw = 0
681
682 self.scale_factor = (1,10,100,1000)[self.scale_factor_raw]
683 self.radius_scaled = radius / self.scale_factor
684 return
685
686 elif bits is not None:
687 self.decode_bits(bits)
688 return
689
690 return
691
692
694 if len(bits) != 90: raise AisUnpackingException('bit length',len(bits))
695 if isinstance(bits,str):
696 bits = BitVector(bitstring = bits)
697 elif isinstance(bits, list) or isinstance(bits,tuple):
698 bits = BitVector ( bitlist = bits)
699
700 self.area_shape = int( bits[:3] )
701 self.scale_factor_raw = int( bits[3:5] )
702 self.scale_factor = (1,10,100,1000)[self.scale_factor_raw]
703 self.lon = binary.signedIntFromBV( bits[ 5:33] ) / 600000.
704 self.lat = binary.signedIntFromBV( bits[33:60] ) / 600000.
705 self.radius_scaled = int( bits[60:72] )
706
707 self.radius = self.radius_scaled * self.scale_factor
708
709 spare = int( bits[72:90] )
710
711
712
714 'Build a BitVector for this area'
715 bvList = []
716 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.area_shape ), 3) )
717 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.scale_factor_raw), 2 ) )
718 bvList.append( binary.bvFromSignedInt( int(self.lon*600000), 28 ) )
719 bvList.append( binary.bvFromSignedInt( int(self.lat*600000), 27 ) )
720 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.radius_scaled), 12 ) )
721 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 18 ) )
722 bv = binary.joinBV(bvList)
723 if 90 != len(bv):
724 print 'len:',[len(b) for b in bvList]
725 raise AisPackingException('area not 90 bits',len(bv))
726
727 return bv
728
730 if self.radius == 0.:
731 return 'AreaNoticeCirclePt: Point at (%.4f,%.4f)' % (self.lon,self.lat)
732 return 'AreaNoticeCirclePt: Circle centered at (%.4f,%.4f) - radius %dm' % (self.lon,self.lat,self.radius)
733
734
735
736
738
739
740 if self.radius <= 0.01:
741 return shapely.geometry.Point(self.lon,self.lat)
742
743
744 zone = lon_to_utm_zone(self.lon)
745 params = {'proj':'utm','zone':zone}
746 proj = Proj(params)
747
748 utm_center = proj(self.lon,self.lat)
749 pt = shapely.geometry.Point(utm_center)
750 circle_utm = pt.buffer(self.radius)
751
752 circle = shapely.geometry.Polygon( [ proj(pt[0],pt[1],inverse=True) for pt in circle_utm.boundary.coords])
753
754 return circle
755
756 @property
758 'Provide a Geo Interface for GeoJSON serialization'
759
760
761
762 if self.radius == 0.:
763 return {'area_shape': self.area_shape,
764 'area_shape_name': 'point',
765 'geometry': {'type': 'Point', 'coordinates': [self.lon, self.lat] }
766 }
767
768
769 r = {
770 'area_shape': self.area_shape,
771 'area_shape_name': 'circle',
772 'radius':self.radius,
773 'geometry': {'type': 'Polygon', 'coordinates': tuple(self.geom().boundary.coords) },
774
775
776 }
777 return r
778
780 area_shape = 1
781 - def __init__(self, lon=None, lat=None, east_dim=0, north_dim=0, orientation_deg=0, bits=None):
782 '''
783 Rotatable rectangle
784 @param lon: WGS84 longitude
785 @param lat: WGS84 latitude
786 @param east_dim: width in meters (this gets confusing for larger angles). 0 is a north-south line
787 @param north_dim: height in meters (this gets confusing for larger angles). 0 is an east-west line
788 @param orientation_deg: degrees CW
789
790 @todo: great get/set for dimensions and allow for setting scale factor.
791 @todo: or just over rule the attribute get and sets
792 @todo: allow user to force the scale factor
793 @todo: Should this be raising a ValueError
794 '''
795 if lon is not None:
796 assert lon >= -180. and lon <= 180.
797 self.lon = lon
798 assert lat >= -90. and lat <= 90.
799 self.lat = lat
800
801 assert 0 <= east_dim and east_dim <= 255000
802 assert 0 <= north_dim and north_dim <= 255000
803
804 assert 0 <= orientation_deg and orientation_deg < 360
805
806 if east_dim / 100. >= 255 or north_dim / 100. >= 255: self.scale_factor_raw = 3
807 elif east_dim / 10. >= 255 or north_dim / 100. >= 255: self.scale_factor_raw = 2
808 elif east_dim >= 255 or north_dim >= 255: self.scale_factor_raw = 1
809 else: self.scale_factor_raw = 0
810 self.scale_factor = (1,10,100,1000)[self.scale_factor_raw]
811
812 self.e_dim = east_dim
813 self.n_dim = north_dim
814 self.e_dim_scaled = east_dim / self.scale_factor
815 self.n_dim_scaled = east_dim / self.scale_factor
816
817 self.orientation_deg = orientation_deg
818
819 elif bits is not None:
820 self.decode_bits(bits)
821
823 if len(bits) != 90: raise AisUnpackingException('bit length',len(bits))
824 if isinstance(bits,str):
825 bits = BitVector(bitstring = bits)
826 elif isinstance(bits, list) or isinstance(bits,tuple):
827 bits = BitVector ( bitlist = bits)
828
829 self.area_shape = int( bits[:3] )
830 self.scale_factor = int( bits[3:5] )
831 self.lon = binary.signedIntFromBV( bits[ 5:33] ) / 600000.
832 self.lat = binary.signedIntFromBV( bits[33:60] ) / 600000.
833 self.e_dim_scaled = int ( bits[60:68] )
834 self.n_dim_scaled = int ( bits[68:76] )
835
836 self.e_dim = self.e_dim_scaled * (1,10,100,1000)[self.scale_factor]
837 self.n_dim = self.n_dim_scaled * (1,10,100,1000)[self.scale_factor]
838
839 self.orientation_deg = int ( bits[76:85] )
840
841 self.spare = int ( bits[85:90] )
842
844 bvList = []
845 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 3 ) )
846
847 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.scale_factor_raw), 2 ) )
848 bvList.append( binary.bvFromSignedInt( int(self.lon*600000), 28 ) )
849 bvList.append( binary.bvFromSignedInt( int(self.lat*600000), 27 ) )
850
851 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.e_dim_scaled), 8 ) )
852 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.n_dim_scaled), 8 ) )
853 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.orientation_deg), 9 ) )
854 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 5 ) )
855
856 bv = binary.joinBV(bvList)
857 assert 90==len(bv)
858 return bv
859
861 return 'AreaNoticeRectangle: (%.4f,%.4f) [%d,%d]m rot: %d deg' % (self.lon,self.lat,self.e_dim,self.n_dim,self.orientation_deg)
862
865
866
868 'return shapely geometry object'
869 zone = lon_to_utm_zone(self.lon)
870 params = {'proj':'utm', 'zone':zone}
871 proj = Proj(params)
872
873 p1 = proj(self.lon,self.lat)
874
875 pts = [(0,0), (self.e_dim,0), (self.e_dim,self.n_dim), (0,self.n_dim)]
876
877
878 rot = deg2rad(-self.orientation_deg)
879 pts = [vec_rot(pt,rot) for pt in pts]
880
881 pts = [vec_add(p1,pt) for pt in pts]
882 pts = [proj(*pt,inverse=True) for pt in pts]
883
884 return shapely.geometry.Polygon(pts)
885
886 @property
888 '''Provide a Geo Interface for GeoJSON serialization
889 @todo: Write the code to build the polygon with rotation'''
890 r = {
891 'area_shape': self.area_shape, 'area_shape_name': 'rectangle',
892 'orientation': self.orientation_deg,
893 'e_dim': self.e_dim, 'n_dim': self.n_dim,
894 'geometry': {'type':'Polygon', 'coordinates': tuple(self.geom().boundary.coords) },
895 }
896
897 return r
898
901 area_shape = 2
902 - def __init__(self, lon=None, lat=None, radius=0, left_bound_deg=0, right_bound_deg=0, bits=None):
903 '''
904 A pie slice
905
906 @param lon: WGS84 longitude
907 @param lat: WGS84 latitude
908 @param radius: width in meters
909 @param left_bound_deg: Orientation of the left boundary. CW from True North
910 @param right_bound_deg: Orientation of the right boundary. CW from True North
911
912 @todo: great get/set for dimensions and allow for setting scale factor.
913 @todo: or just over rule the attribute get and sets
914 @todo: allow user to force the scale factor
915 @todo: Should this be raising a ValueError
916 '''
917 if lon is not None:
918 assert lon >= -180. and lon <= 180.
919 self.lon = lon
920 assert lat >= -90. and lat <= 90.
921 self.lat = lat
922
923 assert 0 <= radius and radius <= 25500
924
925 assert 0 <= left_bound_deg and left_bound_deg < 360
926 assert 0 <= right_bound_deg and right_bound_deg < 360
927
928 assert left_bound_deg <= right_bound_deg
929
930 if radius / 100. >= 4095: self.scale_factor_raw = 3
931 elif radius / 10. > 4095: self.scale_factor_raw = 2
932 elif radius > 4095: self.scale_factor_raw = 1
933 else: self.scale_factor_raw = 0
934 self.scale_factor = (1,10,100,1000)[self.scale_factor_raw]
935 self.radius = radius
936 self.radius_scaled = int( radius / self.scale_factor)
937
938 self.left_bound_deg = left_bound_deg
939 self.right_bound_deg = right_bound_deg
940
941 elif bits is not None:
942 self.decode_bits(bits)
943
945 if len(bits) != 90: raise AisUnpackingException('bit length',len(bits))
946 if isinstance(bits,str):
947 bits = BitVector(bitstring = bits)
948 elif isinstance(bits, list) or isinstance(bits,tuple):
949 bits = BitVector ( bitlist = bits)
950
951 self.area_shape = int( bits[:3] )
952 self.scale_factor = int( bits[3:5] )
953 self.lon = binary.signedIntFromBV( bits[ 5:33] ) / 600000.
954 self.lat = binary.signedIntFromBV( bits[33:60] ) / 600000.
955 self.radius_scaled = int ( bits[60:72] )
956
957 self.radius = self.radius_scaled * (1,10,100,1000)[self.scale_factor]
958
959 self.left_bound_deg = int ( bits[72:81] )
960 self.right_bound_deg = int ( bits[81:90] )
961
962
964
965 'Build a BitVector for this area'
966 bvList = []
967 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.area_shape ), 3) )
968 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.scale_factor_raw), 2 ) )
969 bvList.append( binary.bvFromSignedInt( int(self.lon*600000), 28 ) )
970 bvList.append( binary.bvFromSignedInt( int(self.lat*600000), 27 ) )
971 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.radius_scaled), 12 ) )
972 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.left_bound_deg), 9 ) )
973 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.right_bound_deg), 9 ) )
974
975
976 bv = binary.joinBV(bvList)
977 assert 90==len(bv)
978 return bv
979
981 return 'AreaNoticeSector: (%.4f,%.4f) %d rot: %d to %d deg' % (self.lon, self.lat, self.radius,
982 self.left_bound_deg, self.right_bound_deg)
985
987 'return shapely geometry object'
988 zone = lon_to_utm_zone(self.lon)
989 params = {'proj':'utm', 'zone':zone}
990 proj = Proj(params)
991
992 p1 = proj(self.lon,self.lat)
993
994 pts = [ vec_rot( (0,self.radius), deg2rad(-angle) ) for angle in frange(self.left_bound_deg, self.right_bound_deg+0.01, 0.5) ]
995 pts = [(0,0),] + pts + [(0,0),]
996
997 pts = [vec_add(p1,pt) for pt in pts]
998 pts = [proj(*pt,inverse=True) for pt in pts]
999
1000 return shapely.geometry.Polygon(pts)
1001
1002 @property
1004 '''Provide a Geo Interface for GeoJSON serialization
1005 @todo: Write the code to build the polygon with rotation'''
1006 r = {
1007 'area_shape': self.area_shape, 'area_shape_name': 'sector',
1008 'left_bound': self.left_bound_deg,
1009 'right_bound': self.right_bound_deg,
1010 'radius': self.radius,
1011 'geometry': {'type':'Polygon', 'coordinates': tuple(self.geom().boundary.coords) },
1012 }
1013
1014 return r
1015
1017 area_shape = 3
1018 - def __init__(self, points=None,
1019 lon=None, lat=None,
1020 bits=None):
1021 '''A line or open area. If an area, this is the area to the
1022 left of the line. The line starts at the prior line. Must set p1
1023 or provide bits. You will not be able to get the geometry if you
1024 do not provide a lon,lat for the starting point
1025
1026 @param points: 1 to 4 relative offsets (angle in degrees [0..360] , distance in meters)
1027 @param lon: WGS84 longitude of the starting point. Must match the previous point
1028 @param lat: WGS84 longitude of the starting point. Must match the previous point
1029 @param bits: bits to decode from
1030 @todo: FIX: make sure that the AreaNotice decode bits passes the lon, lat
1031 @todo: FIX: Handle sectors that cross 0/360
1032 '''
1033
1034 if lon is not None:
1035 assert lon >= -180. and lon <= 180.
1036 self.lon = lon
1037 assert lat >= -90. and lat <= 90.
1038 self.lat = lat
1039
1040
1041 if points is not None:
1042 assert len(points)>0 and len(points)<5
1043 self.points = points
1044
1045 max_dist = max([pt[1] for pt in points])
1046 if max_dist / 100. >= 2047: self.scale_factor_raw = 3
1047 elif max_dist / 10. > 2047: self.scale_factor_raw = 2
1048 elif max_dist > 2047: self.scale_factor_raw = 1
1049 else: self.scale_factor_raw = 0
1050 self.scale_factor = (1,10,100,1000)[self.scale_factor_raw]
1051
1052 elif bits is not None:
1053 self.decode_bits(bits)
1054
1056 if len(bits) != 90: raise AisUnpackingException('bit length',len(bits))
1057 if isinstance(bits,str):
1058 bits = BitVector(bitstring = bits)
1059 elif isinstance(bits, list) or isinstance(bits,tuple):
1060 bits = BitVector ( bitlist = bits)
1061
1062 self.area_shape = int( bits[:3] )
1063 self.scale_factor = int( bits[3:5] )
1064
1065 self.points = []
1066 for i in range(4):
1067 base = 5 + i*21
1068 angle = int ( bits[base:base+10] )
1069 dist_scaled = int ( bits[base+10:base+10+11] )
1070 dist = dist_scaled * (1,10,100,1000)[self.scale_factor]
1071 self.points.append((angle,dist))
1072 if 720 == dist_scaled:
1073 break
1074
1075
1077 'Build a BitVector for this area'
1078 bvList = []
1079 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 3 ) )
1080
1081 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.scale_factor_raw), 2 ) )
1082
1083 bvList = []
1084 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.area_shape ), 3) )
1085 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.scale_factor_raw), 2 ) )
1086
1087
1088 for pt in self.points:
1089
1090
1091 bvList.append( binary.setBitVectorSize( BitVector(intVal=pt[0]), 10 ) )
1092 bvList.append( binary.setBitVectorSize( BitVector(intVal=pt[1] / self.scale_factor), 11 ) )
1093
1094 for i in range(4 - len(self.points)):
1095 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 21 ) )
1096
1097 bvList.append( BitVector(intVal=0) )
1098
1099 bv = binary.joinBV(bvList)
1100 if len(bv) != 90:
1101 print 'len:',[len(b) for b in bvList]
1102 raise AisPackingException('area not 90 bits',len(bv))
1103
1104
1105 return bv
1106
1108 return 'AreaNoticePolyline: (%.4f,%.4f) %d points' % ( self.lon, self.lat, len(self.points) )
1109
1110
1111
1112
1114 zone = lon_to_utm_zone(self.lon)
1115 params = {'proj':'utm','zone':zone}
1116 proj = Proj(params)
1117
1118 p1 = proj(self.lon,self.lat)
1119
1120 pts = [(0,0)]
1121 cur = (0,0)
1122 for pt in self.points:
1123 alpha = deg2rad(pt[0])
1124 d = pt[1]
1125 x,y = d * math.sin(alpha), d * math.cos(alpha)
1126 cur = vec_add(cur,(x,y))
1127
1128 pts.append(cur)
1129
1130
1131 pts = [vec_add(p1,pt) for pt in pts]
1132 pts = [proj(*pt,inverse=True) for pt in pts]
1133 return shapely.geometry.LineString(pts)
1134
1135 @property
1137 '''Provide a Geo Interface for GeoJSON serialization
1138 @todo: Write the code to build the polygon with rotation'''
1139 r = {
1140 'area_shape':self.area_shape, 'area_shape_name': 'waypoints/polyline',
1141 'geometry': {'type':'LineString', 'coordinates': tuple(self.geom().coords) },
1142 }
1143
1144 return r
1145
1148 'Polyline that wraps back to the beginning'
1149 area_shape = 4
1150 area_name = 'polygon'
1151
1153 return 'AreaNoticePolylon: (%.4f,%.4f) %d points' % ( self.lon, self.lat, len(self.points) )
1154
1156 zone = lon_to_utm_zone(self.lon)
1157 params = {'proj':'utm','zone':zone}
1158 proj = Proj(params)
1159
1160 p1 = proj(self.lon,self.lat)
1161
1162 pts = [(0,0)]
1163 cur = (0,0)
1164 for pt in self.points:
1165 alpha = deg2rad(pt[0])
1166 d = pt[1]
1167 x,y = d * math.sin(alpha), d * math.cos(alpha)
1168 cur = vec_add(cur,(x,y))
1169 pts.append(cur)
1170
1171
1172
1173
1174 pts = [vec_add(p1,pt) for pt in pts]
1175 pts = [proj(*pt,inverse=True) for pt in pts]
1176 return shapely.geometry.Polygon(pts)
1177
1178 @property
1180 '''Provide a Geo Interface for GeoJSON serialization
1181 @todo: Write the code to build the polygon with rotation'''
1182 r = {
1183 'area_shape':self.area_shape, 'area_shape_name': self.area_name,
1184 'geometry': {'type':'Polygon', 'coordinates': tuple(self.geom().boundary.coords) },
1185 }
1186
1187 return r
1188
1189 -class AreaNoticeFreeText(AreaNoticeSubArea):
1190 area_shape = 4
1191 area_name = 'freetext'
1192 - def __init__(self,text=None, bits=None):
1193 if text is not None:
1194 text = text.upper()
1195
1196
1197 assert len(text) <= 14
1198 for c in text:
1199 assert c in aisstring.characterDict
1200 self.text = text
1201 elif bits is not None:
1202 self.decode_bits(bits)
1203
1204
1205 - def decode_bits(self,bits):
1206 if len(bits) != 90: raise AisUnpackingException('bit length',len(bits))
1207 if isinstance(bits,str):
1208 bits = BitVector(bitstring = bits)
1209 elif isinstance(bits, list) or isinstance(bits,tuple):
1210 bits = BitVector ( bitlist = bits)
1211
1212 area_shape = int( bits[:3] )
1213 assert self.area_shape == area_shape
1214 self.text = aisstring.decode(bits[3:-1])
1215 self.spare = int(bits[-1])
1216
1217 - def get_bits(self):
1218 'Build a BitVector for this area'
1219 bvList = []
1220 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.area_shape), 3 ))
1221 text = self.text.ljust(14,'@')
1222 bvList.append(aisstring.encode(text))
1223 bvList.append( BitVector( bitstring = '000' ) )
1224 bv = binary.joinBV(bvList)
1225 if 90 != len(bv):
1226 print 'len_freetext:',[len(b) for b in bvList]
1227
1228 assert 90==len(bv)
1229 return bv
1230
1231 - def __unicode__(self):
1232 return 'AreaNoticeFreeText: "%s"' % (self.text,)
1233
1234 - def __str__(self):
1235 return self.__unicode__()
1236
1240
1241 @property
1243 'Provide a Geo Interface for GeoJSON serialization'
1244
1245 return {'area_shape': self.area_shape,
1246 'area_shape_name': self.area_name,
1247
1248 }
1249
1251 - def __init__(self,area_type=None,when=None,duration=None,link_id=0, nmea_strings=None):
1252 '''
1253 @param area_type: 0..127 based on table 11.10
1254 @param when: when the notice starts
1255 @type when: datetime (UTC)
1256 @param duration: minutes for the notice to be in effect
1257 @param nmea_strings: Pass 1 or more nmea strings as a list
1258 '''
1259 if nmea_strings != None:
1260 self.decode_nmea(nmea_strings)
1261
1262 elif area_type is not None and when is not None and duration is not None:
1263
1264 assert area_type >= 0 and area_type <= 127
1265 self.area_type = area_type
1266 assert isinstance(when,datetime.datetime)
1267 self.when = when
1268 assert duration < 2**18 - 1
1269 self.duration = duration
1270 self.link_id = link_id
1271
1272 self.areas = []
1273 self.dac = 1
1274 self.fi = 22
1275
1276 BBM.__init__(self, message_id = 8)
1277
1279 result = 'AreaNotice: type=%d start=%s duration=%d m link_id=%d sub-areas: %d' % (
1280 self.area_type, str(self.when), self.duration, self.link_id, len(self.areas) )
1281 if not verbose:
1282 return result
1283 if verbose:
1284 results = [result,]
1285 for item in self.areas:
1286 results.append('\t'+unicode(item))
1287 return '\n'.join(results)
1288
1291
1292 - def html(self, efactory=False):
1293 '''return an embeddable html representation
1294 @param efactory: return lxml E-factory'''
1295 l = E.OL()
1296 for area in self.areas:
1297 l.append(E.LI(str(area)))
1298 if efactory:
1299 return
1300 return lxml.html.tostring(E.DIV(E.P(self.__str__()),l))
1301
1302
1303 @property
1305 'Return dictionary compatible with GeoJSON-AIVD'
1306 try:
1307 repeat = self.repeat_indicator
1308 except:
1309 repeat = 0
1310 if repeat is None: repeat = 0
1311
1312 try:
1313 mmsi = self.source_mmsi
1314 except:
1315 mmsi = 0
1316
1317 r = {
1318 'msgtype':self.message_id,
1319 'repeat': repeat,
1320 'mmsi': mmsi,
1321 "bbm": {
1322 'bbm_type':(self.dac,self.fi),
1323 'bbm_name':'area_notice',
1324 'areas': []
1325 }
1326 }
1327
1328
1329
1330 for area in self.areas:
1331
1332 r['bbm']['areas'].append(area.__geo_interface__)
1333
1334 return r
1335
1337
1338 assert len(self.areas) < 10
1339 self.areas.append(area)
1340
1341 - def get_bits(self,include_bin_hdr=False, mmsi=None, no_include_dac_fi=False):
1342 '''@param include_bin_hdr: If true, include the standard message header with source mmsi'''
1343 bvList = []
1344 if include_bin_hdr:
1345 bvList.append( binary.setBitVectorSize( BitVector(intVal=8), 6 ) )
1346 bvList.append( binary.setBitVectorSize( BitVector(intVal=0), 2 ) )
1347 bvList.append( binary.setBitVectorSize( BitVector(intVal=mmsi), 30 ) )
1348
1349 if include_bin_hdr or not no_include_dac_fi:
1350 bvList.append( BitVector( bitstring = '00' ) )
1351 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.dac), 10 ) )
1352 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.fi), 6 ) )
1353
1354 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.link_id), 10 ) )
1355 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.area_type), 7 ) )
1356
1357 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.when.month), 4 ) )
1358 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.when.day), 5 ) )
1359 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.when.hour), 5 ) )
1360 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.when.minute), 6 ) )
1361
1362 bvList.append( binary.setBitVectorSize( BitVector(intVal=self.duration), 18 ) )
1363
1364 stdlen = sum([len(b) for b in bvList])
1365
1366 for i,area in enumerate(self.areas):
1367 bvList.append(area.get_bits())
1368 return binary.joinBV(bvList)
1369
1370
1371
1372
1373
1374