1
2
3 __version__ = '$Revision: 4791 $'.split()[1]
4 __date__ = '$Date: 2007-11-18 $'.split()[1]
5 __author__ = 'xmlbinmsg'
6
7 __doc__='''
8
9 Autogenerated python functions to serialize/deserialize binary messages.
10
11 Generated by: ./aisxmlbinmsg2py.py
12
13 Need to then wrap these functions with the outer AIS packet and then
14 convert the whole binary blob to a NMEA string. Those functions are
15 not currently provided in this file.
16
17 serialize: python to ais binary
18 deserialize: ais binary to python
19
20 The generated code uses translators.py, binary.py, and aisstring.py
21 which should be packaged with the resulting files.
22
23
24 @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3
25 @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>}
26
27 @author: '''+__author__+'''
28 @version: ''' + __version__ +'''
29 @var __date__: Date of last svn commit
30 @undocumented: __version__ __author__ __doc__ parser
31 @status: under development
32 @license: Generated code has no license
33 @todo: FIX: put in a description of the message here with fields and types.
34 '''
35
36 import sys
37 from decimal import Decimal
38 from BitVector import BitVector
39
40 import binary, aisstring
41
42
43 TrueBV = BitVector(bitstring="1")
44 "Why always rebuild the True bit? This should speed things up a bunch"
45 FalseBV = BitVector(bitstring="0")
46 "Why always rebuild the False bit? This should speed things up a bunch"
47
48
49 fieldList = (
50 'MessageID',
51 'RepeatIndicator',
52 'UserID',
53 'Spare',
54 'dac',
55 'fid',
56 'numreports',
57 'center_longitude',
58 'center_latitude',
59 'time_day',
60 'time_hour',
61 'time_min',
62 'timetoexpire',
63 'radius',
64 )
65
66 fieldListPostgres = (
67 'MessageID',
68 'RepeatIndicator',
69 'UserID',
70 'Spare',
71 'dac',
72 'fid',
73 'numreports',
74 'center',
75 'time_day',
76 'time_hour',
77 'time_min',
78 'timetoexpire',
79 'radius',
80 )
81
82 toPgFields = {
83 'center_longitude':'center',
84 'center_latitude':'center',
85 }
86 '''
87 Go to the Postgis field names from the straight field name
88 '''
89
90 fromPgFields = {
91 'center':('center_longitude','center_latitude',),
92 }
93 '''
94 Go from the Postgis field names to the straight field name
95 '''
96
97 pgTypes = {
98 'center':'POINT',
99 }
100 '''
101 Lookup table for each postgis field name to get its type.
102 '''
103
104 -def encode(params, validate=False):
105 '''Create a whalenotice binary message payload to pack into an AIS Msg whalenotice.
106
107 Fields in params:
108 - MessageID(uint): AIS message number. Must be 8 (field automatically set to "8")
109 - RepeatIndicator(uint): Indicated how many times a message has been repeated
110 - UserID(uint): Unique ship identification number (MMSI)
111 - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
112 - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
113 - fid(uint): Functional IDentifier - 63 for the Whale Notice (field automatically set to "63")
114 - numreports(uint): Number of detection reports filled out in this message
115 - center_longitude(decimal): Center of the detection zone. East West location
116 - center_latitude(decimal): Center of the detection zone. North South location
117 - time_day(uint): Time of most recent whale detection. UTC day of the month 1..31
118 - time_hour(uint): Time of most recent whale detection. UTC hours 0..23
119 - time_min(uint): Time of most recent whale detection. UTC minutes
120 - timetoexpire(uint): Seconds from the detection time until the notice expires
121 - radius(uint): Distance from center of detection zone (lat/lon above)
122 @param params: Dictionary of field names/values. Throws a ValueError exception if required is missing
123 @param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.
124 @rtype: BitVector
125 @return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8
126 @note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.
127 '''
128
129 bvList = []
130 bvList.append(binary.setBitVectorSize(BitVector(intVal=8),6))
131 if 'RepeatIndicator' in params:
132 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['RepeatIndicator']),2))
133 else:
134 bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
135 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['UserID']),30))
136 bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
137 bvList.append(binary.setBitVectorSize(BitVector(intVal=366),10))
138 bvList.append(binary.setBitVectorSize(BitVector(intVal=63),6))
139 if 'numreports' in params:
140 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['numreports']),2))
141 else:
142 bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
143 if 'center_longitude' in params:
144 bvList.append(binary.bvFromSignedInt(int(Decimal(params['center_longitude'])*Decimal('600000')),28))
145 else:
146 bvList.append(binary.bvFromSignedInt(108600000,28))
147 if 'center_latitude' in params:
148 bvList.append(binary.bvFromSignedInt(int(Decimal(params['center_latitude'])*Decimal('600000')),27))
149 else:
150 bvList.append(binary.bvFromSignedInt(54600000,27))
151 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['time_day']),5))
152 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['time_hour']),5))
153 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['time_min']),6))
154 if 'timetoexpire' in params:
155 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['timetoexpire']),16))
156 else:
157 bvList.append(binary.setBitVectorSize(BitVector(intVal=65535),16))
158 if 'radius' in params:
159 bvList.append(binary.setBitVectorSize(BitVector(intVal=params['radius']),16))
160 else:
161 bvList.append(binary.setBitVectorSize(BitVector(intVal=65534),16))
162
163 return binary.joinBV(bvList)
164
165 -def decode(bv, validate=False):
166 '''Unpack a whalenotice message
167
168 Fields in params:
169 - MessageID(uint): AIS message number. Must be 8 (field automatically set to "8")
170 - RepeatIndicator(uint): Indicated how many times a message has been repeated
171 - UserID(uint): Unique ship identification number (MMSI)
172 - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
173 - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
174 - fid(uint): Functional IDentifier - 63 for the Whale Notice (field automatically set to "63")
175 - numreports(uint): Number of detection reports filled out in this message
176 - center_longitude(decimal): Center of the detection zone. East West location
177 - center_latitude(decimal): Center of the detection zone. North South location
178 - time_day(uint): Time of most recent whale detection. UTC day of the month 1..31
179 - time_hour(uint): Time of most recent whale detection. UTC hours 0..23
180 - time_min(uint): Time of most recent whale detection. UTC minutes
181 - timetoexpire(uint): Seconds from the detection time until the notice expires
182 - radius(uint): Distance from center of detection zone (lat/lon above)
183 @type bv: BitVector
184 @param bv: Bits defining a message
185 @param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.
186 @rtype: dict
187 @return: params
188 '''
189
190
191
192
193 r = {}
194 r['MessageID']=8
195 r['RepeatIndicator']=int(bv[6:8])
196 r['UserID']=int(bv[8:38])
197 r['Spare']=0
198 r['dac']=366
199 r['fid']=63
200 r['numreports']=int(bv[56:58])
201 r['center_longitude']=Decimal(binary.signedIntFromBV(bv[58:86]))/Decimal('600000')
202 r['center_latitude']=Decimal(binary.signedIntFromBV(bv[86:113]))/Decimal('600000')
203 r['time_day']=int(bv[113:118])
204 r['time_hour']=int(bv[118:123])
205 r['time_min']=int(bv[123:129])
206 r['timetoexpire']=int(bv[129:145])
207 r['radius']=int(bv[145:161])
208 return r
209
212
215
218
221
224
227
229 return int(bv[56:58])
230
233
236
238 return int(bv[113:118])
239
241 return int(bv[118:123])
242
244 return int(bv[123:129])
245
247 return int(bv[129:145])
248
250 return int(bv[145:161])
251
252
254 out.write("<h3>whalenotice</h3>\n")
255 out.write("<table border=\"1\">\n")
256 out.write("<tr bgcolor=\"orange\">\n")
257 out.write("<th align=\"left\">Field Name</th>\n")
258 out.write("<th align=\"left\">Type</th>\n")
259 out.write("<th align=\"left\">Value</th>\n")
260 out.write("<th align=\"left\">Value in Lookup Table</th>\n")
261 out.write("<th align=\"left\">Units</th>\n")
262 out.write("\n")
263 out.write("<tr>\n")
264 out.write("<td>MessageID</td>\n")
265 out.write("<td>uint</td>\n")
266 if 'MessageID' in params:
267 out.write(" <td>"+str(params['MessageID'])+"</td>\n")
268 out.write(" <td>"+str(params['MessageID'])+"</td>\n")
269 out.write("</tr>\n")
270 out.write("\n")
271 out.write("<tr>\n")
272 out.write("<td>RepeatIndicator</td>\n")
273 out.write("<td>uint</td>\n")
274 if 'RepeatIndicator' in params:
275 out.write(" <td>"+str(params['RepeatIndicator'])+"</td>\n")
276 if str(params['RepeatIndicator']) in RepeatIndicatorDecodeLut:
277 out.write("<td>"+RepeatIndicatorDecodeLut[str(params['RepeatIndicator'])]+"</td>")
278 else:
279 out.write("<td><i>Missing LUT entry</i></td>")
280 out.write("</tr>\n")
281 out.write("\n")
282 out.write("<tr>\n")
283 out.write("<td>UserID</td>\n")
284 out.write("<td>uint</td>\n")
285 if 'UserID' in params:
286 out.write(" <td>"+str(params['UserID'])+"</td>\n")
287 out.write(" <td>"+str(params['UserID'])+"</td>\n")
288 out.write("</tr>\n")
289 out.write("\n")
290 out.write("<tr>\n")
291 out.write("<td>Spare</td>\n")
292 out.write("<td>uint</td>\n")
293 if 'Spare' in params:
294 out.write(" <td>"+str(params['Spare'])+"</td>\n")
295 out.write(" <td>"+str(params['Spare'])+"</td>\n")
296 out.write("</tr>\n")
297 out.write("\n")
298 out.write("<tr>\n")
299 out.write("<td>dac</td>\n")
300 out.write("<td>uint</td>\n")
301 if 'dac' in params:
302 out.write(" <td>"+str(params['dac'])+"</td>\n")
303 out.write(" <td>"+str(params['dac'])+"</td>\n")
304 out.write("</tr>\n")
305 out.write("\n")
306 out.write("<tr>\n")
307 out.write("<td>fid</td>\n")
308 out.write("<td>uint</td>\n")
309 if 'fid' in params:
310 out.write(" <td>"+str(params['fid'])+"</td>\n")
311 out.write(" <td>"+str(params['fid'])+"</td>\n")
312 out.write("</tr>\n")
313 out.write("\n")
314 out.write("<tr>\n")
315 out.write("<td>numreports</td>\n")
316 out.write("<td>uint</td>\n")
317 if 'numreports' in params:
318 out.write(" <td>"+str(params['numreports'])+"</td>\n")
319 out.write(" <td>"+str(params['numreports'])+"</td>\n")
320 out.write("</tr>\n")
321 out.write("\n")
322 out.write("<tr>\n")
323 out.write("<td>center_longitude</td>\n")
324 out.write("<td>decimal</td>\n")
325 if 'center_longitude' in params:
326 out.write(" <td>"+str(params['center_longitude'])+"</td>\n")
327 out.write(" <td>"+str(params['center_longitude'])+"</td>\n")
328 out.write("<td>degrees</td>\n")
329 out.write("</tr>\n")
330 out.write("\n")
331 out.write("<tr>\n")
332 out.write("<td>center_latitude</td>\n")
333 out.write("<td>decimal</td>\n")
334 if 'center_latitude' in params:
335 out.write(" <td>"+str(params['center_latitude'])+"</td>\n")
336 out.write(" <td>"+str(params['center_latitude'])+"</td>\n")
337 out.write("<td>degrees</td>\n")
338 out.write("</tr>\n")
339 out.write("\n")
340 out.write("<tr>\n")
341 out.write("<td>time_day</td>\n")
342 out.write("<td>uint</td>\n")
343 if 'time_day' in params:
344 out.write(" <td>"+str(params['time_day'])+"</td>\n")
345 out.write(" <td>"+str(params['time_day'])+"</td>\n")
346 out.write("</tr>\n")
347 out.write("\n")
348 out.write("<tr>\n")
349 out.write("<td>time_hour</td>\n")
350 out.write("<td>uint</td>\n")
351 if 'time_hour' in params:
352 out.write(" <td>"+str(params['time_hour'])+"</td>\n")
353 out.write(" <td>"+str(params['time_hour'])+"</td>\n")
354 out.write("</tr>\n")
355 out.write("\n")
356 out.write("<tr>\n")
357 out.write("<td>time_min</td>\n")
358 out.write("<td>uint</td>\n")
359 if 'time_min' in params:
360 out.write(" <td>"+str(params['time_min'])+"</td>\n")
361 out.write(" <td>"+str(params['time_min'])+"</td>\n")
362 out.write("</tr>\n")
363 out.write("\n")
364 out.write("<tr>\n")
365 out.write("<td>timetoexpire</td>\n")
366 out.write("<td>uint</td>\n")
367 if 'timetoexpire' in params:
368 out.write(" <td>"+str(params['timetoexpire'])+"</td>\n")
369 if str(params['timetoexpire']) in timetoexpireDecodeLut:
370 out.write("<td>"+timetoexpireDecodeLut[str(params['timetoexpire'])]+"</td>")
371 else:
372 out.write("<td><i>Missing LUT entry</i></td>")
373 out.write("<td>seconds</td>\n")
374 out.write("</tr>\n")
375 out.write("\n")
376 out.write("<tr>\n")
377 out.write("<td>radius</td>\n")
378 out.write("<td>uint</td>\n")
379 if 'radius' in params:
380 out.write(" <td>"+str(params['radius'])+"</td>\n")
381 out.write(" <td>"+str(params['radius'])+"</td>\n")
382 out.write("<td>m</td>\n")
383 out.write("</tr>\n")
384 out.write("</table>\n")
385
386
388 '''KML (Keyhole Markup Language) for Google Earth, but without the header/footer'''
389 out.write("\ <Placemark>\n")
390 out.write("\t <name>"+str(params['stationsid'])+"</name>\n")
391 out.write("\t\t<description>\n")
392 import StringIO
393 buf = StringIO.StringIO()
394 printHtml(params,buf)
395 import cgi
396 out.write(cgi.escape(buf.getvalue()))
397 out.write("\t\t</description>\n")
398 out.write("\t\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\n")
399 out.write("\t\t<Point>\n")
400 out.write("\t\t\t<coordinates>")
401 out.write(str(params['center_longitude']))
402 out.write(',')
403 out.write(str(params['center_latitude']))
404 out.write(",0</coordinates>\n")
405 out.write("\t\t</Point>\n")
406 out.write("\t</Placemark>\n")
407
408 -def printFields(params, out=sys.stdout, format='std', fieldList=None, dbType='postgres'):
409 '''Print a whalenotice message to stdout.
410
411 Fields in params:
412 - MessageID(uint): AIS message number. Must be 8 (field automatically set to "8")
413 - RepeatIndicator(uint): Indicated how many times a message has been repeated
414 - UserID(uint): Unique ship identification number (MMSI)
415 - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
416 - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
417 - fid(uint): Functional IDentifier - 63 for the Whale Notice (field automatically set to "63")
418 - numreports(uint): Number of detection reports filled out in this message
419 - center_longitude(decimal): Center of the detection zone. East West location
420 - center_latitude(decimal): Center of the detection zone. North South location
421 - time_day(uint): Time of most recent whale detection. UTC day of the month 1..31
422 - time_hour(uint): Time of most recent whale detection. UTC hours 0..23
423 - time_min(uint): Time of most recent whale detection. UTC minutes
424 - timetoexpire(uint): Seconds from the detection time until the notice expires
425 - radius(uint): Distance from center of detection zone (lat/lon above)
426 @param params: Dictionary of field names/values.
427 @param out: File like object to write to
428 @rtype: stdout
429 @return: text to out
430 '''
431
432 if 'std'==format:
433 out.write("whalenotice:\n")
434 if 'MessageID' in params: out.write(" MessageID: "+str(params['MessageID'])+"\n")
435 if 'RepeatIndicator' in params: out.write(" RepeatIndicator: "+str(params['RepeatIndicator'])+"\n")
436 if 'UserID' in params: out.write(" UserID: "+str(params['UserID'])+"\n")
437 if 'Spare' in params: out.write(" Spare: "+str(params['Spare'])+"\n")
438 if 'dac' in params: out.write(" dac: "+str(params['dac'])+"\n")
439 if 'fid' in params: out.write(" fid: "+str(params['fid'])+"\n")
440 if 'numreports' in params: out.write(" numreports: "+str(params['numreports'])+"\n")
441 if 'center_longitude' in params: out.write(" center_longitude: "+str(params['center_longitude'])+"\n")
442 if 'center_latitude' in params: out.write(" center_latitude: "+str(params['center_latitude'])+"\n")
443 if 'time_day' in params: out.write(" time_day: "+str(params['time_day'])+"\n")
444 if 'time_hour' in params: out.write(" time_hour: "+str(params['time_hour'])+"\n")
445 if 'time_min' in params: out.write(" time_min: "+str(params['time_min'])+"\n")
446 if 'timetoexpire' in params: out.write(" timetoexpire: "+str(params['timetoexpire'])+"\n")
447 if 'radius' in params: out.write(" radius: "+str(params['radius'])+"\n")
448 elif 'csv'==format:
449 if None == options.fieldList:
450 options.fieldList = fieldList
451 needComma = False;
452 for field in fieldList:
453 if needComma: out.write(',')
454 needComma = True
455 if field in params:
456 out.write(str(params[field]))
457
458 out.write("\n")
459 elif 'html'==format:
460 printHtml(params,out)
461 elif 'sql'==format:
462 sqlInsertStr(params,out,dbType=dbType)
463 elif 'kml'==format:
464 printKml(params,out)
465 elif 'kml-full'==format:
466 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
467 out.write("<kml xmlns=\"http://earth.google.com/kml/2.1\">\n")
468 out.write("<Document>\n")
469 out.write(" <name>whalenotice</name>\n")
470 printKml(params,out)
471 out.write("</Document>\n")
472 out.write("</kml>\n")
473 else:
474 print "ERROR: unknown format:",format
475 assert False
476
477 return
478
479 RepeatIndicatorEncodeLut = {
480 'default':'0',
481 'do not repeat any more':'3',
482 }
483
484 RepeatIndicatorDecodeLut = {
485 '0':'default',
486 '3':'do not repeat any more',
487 }
488
489 timetoexpireEncodeLut = {
490 'No detection/notice active in region':'0',
491 }
492
493 timetoexpireDecodeLut = {
494 '0':'No detection/notice active in region',
495 }
496
497
498
499
500
501 -def sqlCreateStr(outfile=sys.stdout, fields=None, extraFields=None
502 ,addCoastGuardFields=True
503 ,dbType='postgres'
504 ):
505 '''
506 Return the SQL CREATE command for this message type
507 @param outfile: file like object to print to.
508 @param fields: which fields to put in the create. Defaults to all.
509 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
510 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
511 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
512 @type addCoastGuardFields: bool
513 @return: sql create string
514 @rtype: str
515
516 @see: sqlCreate
517 '''
518
519 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))
520
521 -def sqlCreate(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
522 '''
523 Return the sqlhelp object to create the table.
524
525 @param fields: which fields to put in the create. Defaults to all.
526 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
527 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
528 @type addCoastGuardFields: bool
529 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
530 @return: An object that can be used to generate a return
531 @rtype: sqlhelp.create
532 '''
533 if None == fields: fields = fieldList
534 import sqlhelp
535 c = sqlhelp.create('whalenotice',dbType=dbType)
536 c.addPrimaryKey()
537 if 'MessageID' in fields: c.addInt ('MessageID')
538 if 'RepeatIndicator' in fields: c.addInt ('RepeatIndicator')
539 if 'UserID' in fields: c.addInt ('UserID')
540 if 'Spare' in fields: c.addInt ('Spare')
541 if 'dac' in fields: c.addInt ('dac')
542 if 'fid' in fields: c.addInt ('fid')
543 if 'numreports' in fields: c.addInt ('numreports')
544 if dbType != 'postgres':
545 if 'center_longitude' in fields: c.addDecimal('center_longitude',8,5)
546 if dbType != 'postgres':
547 if 'center_latitude' in fields: c.addDecimal('center_latitude',8,5)
548 if 'time_day' in fields: c.addInt ('time_day')
549 if 'time_hour' in fields: c.addInt ('time_hour')
550 if 'time_min' in fields: c.addInt ('time_min')
551 if 'timetoexpire' in fields: c.addInt ('timetoexpire')
552 if 'radius' in fields: c.addInt ('radius')
553
554 if addCoastGuardFields:
555
556
557
558
559
560 c.addVarChar('cg_r',15)
561 c.addInt('cg_sec')
562
563 c.addTimestamp('cg_timestamp')
564
565 if dbType == 'postgres':
566
567
568 c.addPostGIS('center','POINT',2,SRID=4326);
569
570 return c
571
572 -def sqlInsertStr(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
573 '''
574 Return the SQL INSERT command for this message type
575 @param params: dictionary of values keyed by field name
576 @param outfile: file like object to print to.
577 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
578 @return: sql create string
579 @rtype: str
580
581 @see: sqlCreate
582 '''
583 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))
584
585
586 -def sqlInsert(params,extraParams=None,dbType='postgres'):
587 '''
588 Give the SQL INSERT statement
589 @param params: dict keyed by field name of values
590 @param extraParams: any extra fields that you have created beyond the normal ais message fields
591 @rtype: sqlhelp.insert
592 @return: insert class instance
593 @todo: allow optional type checking of params?
594 @warning: this will take invalid keys happily and do what???
595 '''
596 import sqlhelp
597 i = sqlhelp.insert('whalenotice',dbType=dbType)
598
599 if dbType=='postgres':
600 finished = []
601 for key in params:
602 if key in finished:
603 continue
604
605 if key not in toPgFields and key not in fromPgFields:
606 if type(params[key])==Decimal: i.add(key,float(params[key]))
607 else: i.add(key,params[key])
608 else:
609 if key in fromPgFields:
610 val = params[key]
611
612 i.addPostGIS(key,val)
613 finished.append(key)
614 else:
615
616 pgName = toPgFields[key]
617
618 valStr=pgTypes[pgName]+'('
619 vals = []
620 for nonPgKey in fromPgFields[pgName]:
621 vals.append(str(params[nonPgKey]))
622 finished.append(nonPgKey)
623 valStr+=' '.join(vals)+')'
624 i.addPostGIS(pgName,valStr)
625 else:
626 for key in params:
627 if type(params[key])==Decimal: i.add(key,float(params[key]))
628 else: i.add(key,params[key])
629
630 if None != extraParams:
631 for key in extraParams:
632 i.add(key,extraParams[key])
633
634 return i
635
636
637
638
639
642 '''
643 Return the LaTeX definition table for this message type
644 @param outfile: file like object to print to.
645 @type outfile: file obj
646 @return: LaTeX table string via the outfile
647 @rtype: str
648
649 '''
650 o = outfile
651
652 o.write('''
653 \\begin{table}%[htb]
654 \\centering
655 \\begin{tabular}{|l|c|l|}
656 \\hline
657 Parameter & Number of bits & Description
658 \\\\ \\hline\\hline
659 MessageID & 6 & AIS message number. Must be 8 \\\\ \hline
660 RepeatIndicator & 2 & Indicated how many times a message has been repeated \\\\ \hline
661 UserID & 30 & Unique ship identification number (MMSI) \\\\ \hline
662 Spare & 2 & Reserved for definition by a regional authority. \\\\ \hline
663 dac & 10 & Designated Area Code - 366 for the United States \\\\ \hline
664 fid & 6 & Functional IDentifier - 63 for the Whale Notice \\\\ \hline
665 numreports & 2 & Number of detection reports filled out in this message \\\\ \hline
666 center\_longitude & 28 & Center of the detection zone. East West location \\\\ \hline
667 center\_latitude & 27 & Center of the detection zone. North South location \\\\ \hline
668 time\_day & 5 & Time of most recent whale detection. UTC day of the month 1..31 \\\\ \hline
669 time\_hour & 5 & Time of most recent whale detection. UTC hours 0..23 \\\\ \hline
670 time\_min & 6 & Time of most recent whale detection. UTC minutes \\\\ \hline
671 timetoexpire & 16 & Seconds from the detection time until the notice expires \\\\ \hline
672 radius & 16 & Distance from center of detection zone (lat/lon above)\\\\ \\hline \\hline
673 Total bits & 161 & Appears to take 1 slot with 7 pad bits to fill the last slot \\\\ \\hline
674 \\end{tabular}
675 \\caption{AIS message number 8: Endangered whale notification binary message}
676 \\label{tab:whalenotice}
677 \\end{table}
678 ''')
679
680
681
682
683
684 -def textDefinitionTable(outfile=sys.stdout
685 ,delim='\t'
686 ):
687 '''
688 Return the text definition table for this message type
689 @param outfile: file like object to print to.
690 @type outfile: file obj
691 @return: text table string via the outfile
692 @rtype: str
693
694 '''
695 o = outfile
696 o.write('''Parameter'''+delim+'Number of bits'''+delim+'''Description
697 MessageID'''+delim+'''6'''+delim+'''AIS message number. Must be 8
698 RepeatIndicator'''+delim+'''2'''+delim+'''Indicated how many times a message has been repeated
699 UserID'''+delim+'''30'''+delim+'''Unique ship identification number (MMSI)
700 Spare'''+delim+'''2'''+delim+'''Reserved for definition by a regional authority.
701 dac'''+delim+'''10'''+delim+'''Designated Area Code - 366 for the United States
702 fid'''+delim+'''6'''+delim+'''Functional IDentifier - 63 for the Whale Notice
703 numreports'''+delim+'''2'''+delim+'''Number of detection reports filled out in this message
704 center_longitude'''+delim+'''28'''+delim+'''Center of the detection zone. East West location
705 center_latitude'''+delim+'''27'''+delim+'''Center of the detection zone. North South location
706 time_day'''+delim+'''5'''+delim+'''Time of most recent whale detection. UTC day of the month 1..31
707 time_hour'''+delim+'''5'''+delim+'''Time of most recent whale detection. UTC hours 0..23
708 time_min'''+delim+'''6'''+delim+'''Time of most recent whale detection. UTC minutes
709 timetoexpire'''+delim+'''16'''+delim+'''Seconds from the detection time until the notice expires
710 radius'''+delim+'''16'''+delim+'''Distance from center of detection zone (lat/lon above)
711 Total bits'''+delim+'''161'''+delim+'''Appears to take 1 slot with 7 pad bits to fill the last slot''')
712
713
714
715
716
717 import unittest
719 '''Return a params file base on the testvalue tags.
720 @rtype: dict
721 @return: params based on testvalue tags
722 '''
723 params = {}
724 params['MessageID'] = 8
725 params['RepeatIndicator'] = 1
726 params['UserID'] = 1193046
727 params['Spare'] = 0
728 params['dac'] = 366
729 params['fid'] = 63
730 params['numreports'] = 3
731 params['center_longitude'] = Decimal('-122.16328055555556')
732 params['center_latitude'] = Decimal('37.424458333333334')
733 params['time_day'] = 28
734 params['time_hour'] = 23
735 params['time_min'] = 45
736 params['timetoexpire'] = 1
737 params['radius'] = 5000
738
739 return params
740
742 '''Use testvalue tag text from each type to build test case the whalenotice message'''
744
745 params = testParams()
746 bits = encode(params)
747 r = decode(bits)
748
749
750 self.failUnlessEqual(r['MessageID'],params['MessageID'])
751 self.failUnlessEqual(r['RepeatIndicator'],params['RepeatIndicator'])
752 self.failUnlessEqual(r['UserID'],params['UserID'])
753 self.failUnlessEqual(r['Spare'],params['Spare'])
754 self.failUnlessEqual(r['dac'],params['dac'])
755 self.failUnlessEqual(r['fid'],params['fid'])
756 self.failUnlessEqual(r['numreports'],params['numreports'])
757 self.failUnlessAlmostEqual(r['center_longitude'],params['center_longitude'],5)
758 self.failUnlessAlmostEqual(r['center_latitude'],params['center_latitude'],5)
759 self.failUnlessEqual(r['time_day'],params['time_day'])
760 self.failUnlessEqual(r['time_hour'],params['time_hour'])
761 self.failUnlessEqual(r['time_min'],params['time_min'])
762 self.failUnlessEqual(r['timetoexpire'],params['timetoexpire'])
763 self.failUnlessEqual(r['radius'],params['radius'])
764
766 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
767 help='decode a "whalenotice" AIS message')
768 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
769 help='encode a "whalenotice" AIS message')
770 parser.add_option('--RepeatIndicator-field', dest='RepeatIndicatorField',default=0,metavar='uint',type='int'
771 ,help='Field parameter value [default: %default]')
772 parser.add_option('--UserID-field', dest='UserIDField',metavar='uint',type='int'
773 ,help='Field parameter value [default: %default]')
774 parser.add_option('--numreports-field', dest='numreportsField',default=0,metavar='uint',type='int'
775 ,help='Field parameter value [default: %default]')
776 parser.add_option('--center_longitude-field', dest='center_longitudeField',default=Decimal('181'),metavar='decimal',type='string'
777 ,help='Field parameter value [default: %default]')
778 parser.add_option('--center_latitude-field', dest='center_latitudeField',default=Decimal('91'),metavar='decimal',type='string'
779 ,help='Field parameter value [default: %default]')
780 parser.add_option('--time_day-field', dest='time_dayField',metavar='uint',type='int'
781 ,help='Field parameter value [default: %default]')
782 parser.add_option('--time_hour-field', dest='time_hourField',metavar='uint',type='int'
783 ,help='Field parameter value [default: %default]')
784 parser.add_option('--time_min-field', dest='time_minField',metavar='uint',type='int'
785 ,help='Field parameter value [default: %default]')
786 parser.add_option('--timetoexpire-field', dest='timetoexpireField',default=65535,metavar='uint',type='int'
787 ,help='Field parameter value [default: %default]')
788 parser.add_option('--radius-field', dest='radiusField',default=65534,metavar='uint',type='int'
789 ,help='Field parameter value [default: %default]')
790
791
792 if __name__=='__main__':
793
794 from optparse import OptionParser
795 parser = OptionParser(usage="%prog [options]",
796 version="%prog "+__version__)
797
798 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
799 help='run the documentation tests')
800 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
801 help='run the unit tests')
802 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
803 help='Make the test output verbose')
804
805
806
807 typeChoices = ('binary','nmeapayload','nmea')
808 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType'
809 ,default='nmeapayload'
810 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')
811
812
813 outputChoices = ('std','html','csv','sql' , 'kml','kml-full')
814 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType'
815 ,default='std'
816 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]')
817
818 parser.add_option('-o','--output',dest='outputFileName',default=None,
819 help='Name of the python file to write [default: stdout]')
820
821 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append',
822 choices=fieldList,
823 help='Which fields to include in the output. Currently only for csv output [default: all]')
824
825 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true',
826 help='Print the field name for csv')
827
828 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true',
829 help='Print out an sql create command for the table.')
830
831 parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true',
832 help='Print a LaTeX table of the type')
833
834 parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true',
835 help='Print delimited table of the type (for Word table importing)')
836 parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\t'
837 ,help='Delimiter for text table [default: \'%default\'](for Word table importing)')
838
839
840 dbChoices = ('sqlite','postgres')
841 parser.add_option('-D','--db-type',dest='dbType',default='postgres'
842 ,choices=dbChoices,type='choice'
843 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]')
844
845 addMsgOptions(parser)
846
847 (options,args) = parser.parse_args()
848 success=True
849
850 if options.doctest:
851 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
852 sys.argv= [sys.argv[0]]
853 if options.verbose: sys.argv.append('-v')
854 import doctest
855 numfail,numtests=doctest.testmod()
856 if numfail==0: print 'ok'
857 else:
858 print 'FAILED'
859 success=False
860
861 if not success: sys.exit('Something Failed')
862 del success
863
864 if options.unittest:
865 sys.argv = [sys.argv[0]]
866 if options.verbose: sys.argv.append('-v')
867 unittest.main()
868
869 outfile = sys.stdout
870 if None!=options.outputFileName:
871 outfile = file(options.outputFileName,'w')
872
873
874 if options.doEncode:
875
876 if None==options.RepeatIndicatorField: parser.error("missing value for RepeatIndicatorField")
877 if None==options.UserIDField: parser.error("missing value for UserIDField")
878 if None==options.numreportsField: parser.error("missing value for numreportsField")
879 if None==options.center_longitudeField: parser.error("missing value for center_longitudeField")
880 if None==options.center_latitudeField: parser.error("missing value for center_latitudeField")
881 if None==options.time_dayField: parser.error("missing value for time_dayField")
882 if None==options.time_hourField: parser.error("missing value for time_hourField")
883 if None==options.time_minField: parser.error("missing value for time_minField")
884 if None==options.timetoexpireField: parser.error("missing value for timetoexpireField")
885 if None==options.radiusField: parser.error("missing value for radiusField")
886 msgDict={
887 'MessageID': '8',
888 'RepeatIndicator': options.RepeatIndicatorField,
889 'UserID': options.UserIDField,
890 'Spare': '0',
891 'dac': '366',
892 'fid': '63',
893 'numreports': options.numreportsField,
894 'center_longitude': options.center_longitudeField,
895 'center_latitude': options.center_latitudeField,
896 'time_day': options.time_dayField,
897 'time_hour': options.time_hourField,
898 'time_min': options.time_minField,
899 'timetoexpire': options.timetoexpireField,
900 'radius': options.radiusField,
901 }
902
903 bits = encode(msgDict)
904 if 'binary'==options.ioType: print str(bits)
905 elif 'nmeapayload'==options.ioType:
906
907 print "bitLen",len(bits)
908 bitLen=len(bits)
909 if bitLen%6!=0:
910 bits = bits + BitVector(size=(6 - (bitLen%6)))
911 print "result:",binary.bitvectoais6(bits)[0]
912
913
914
915 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability")
916 else: sys.exit('ERROR: unknown ioType. Help!')
917
918
919 if options.sqlCreate:
920 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)
921
922 if options.latexDefinitionTable:
923 latexDefinitionTable(outfile)
924
925
926 if options.textDefinitionTable:
927 textDefinitionTable(outfile,options.delimTextDefinitionTable)
928
929 if options.printCsvfieldList:
930
931 if None == options.fieldList: options.fieldList = fieldList
932 import StringIO
933 buf = StringIO.StringIO()
934 for field in options.fieldList:
935 buf.write(field+',')
936 result = buf.getvalue()
937 if result[-1] == ',': print result[:-1]
938 else: print result
939
940 if options.doDecode:
941 if len(args)==0: args = sys.stdin
942 for msg in args:
943 bv = None
944
945 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
946
947
948 bv = binary.ais6tobitvec(msg.split(',')[5])
949 else:
950
951 binaryMsg=True
952 for c in msg:
953 if c not in ('0','1'):
954 binaryMsg=False
955 break
956 if binaryMsg:
957 bv = BitVector(bitstring=msg)
958 else:
959 bv = binary.ais6tobitvec(msg)
960
961 printFields(decode(bv)
962 ,out=outfile
963 ,format=options.outputType
964 ,fieldList=options.fieldList
965 ,dbType=options.dbType
966 )
967