PerlでのMastdon toot
マストドンでの自動トゥート作った。
Perlは、https通信ができるように、必要な対応はしておいてください。
【参考】
<<インスタンス>>は、利用しているインスタンスのURLを入れてください。
<<アクセストークン>>は、マストドンの「アカウント設定」「開発」でアプリを作成したら、設定されるので、それを入れてください。
なお、アプリには、アクセス権「write:statuses」を付与しておいてください。
ちなみに、インスタンスsocial.vivaldi.netは、LWP::UserAgentのUserAgentを拒否しているので、他のUserAgentを設定しました。
#/usr/bin/perl
use utf8;
use strict;
#use warnings;
use Encode qw(encode decode);
use LWP::UserAgent;
use HTTP::Request;
use constant {
##Mastdon
REQUEST_URI_M => 'https://<<インスタンス>>/api/v1/statuses',
ACCESS_TOKEN_M => 'Bearer <<アクセストークン>>',
};
sub tootMessage{
my $message = shift;
my $log = '';
#文字数チェックと改行除く。
my $c_text = $message;
$c_text =~ s/\r/ /sig;
$c_text =~ s/\n/ /sig;
my $length = length($c_text);
if($length > 500){
return("Message length[$length] too much at tootMessage.[$c_text]\n");
}
### リクエストパラメータの準備
my %param = (status => _encode_url($message), visibility => 'public');#visibiltyは適宜変更すること
my $res;
eval{
### リクエストを生成する
my $content = &_get_content(%param);
print $content."\n";
print REQUEST_URI_M."\n";
print ACCESS_TOKEN_M."\n";
my $req = HTTP::Request->new ('POST', REQUEST_URI_M, [Authorization => ACCESS_TOKEN_M, 'Content-Type' => 'application/x-www-form-urlencoded'], $content)
or die 'Failed to initialize HTTP::Request';
### リクエストを投げる
my $ua = LWP::UserAgent->new or die 'Failed to initialize LWP::UserAgent';
$ua->agent("");#UserAgentで拒否しているインスタンスがあるので、拒否しないUserAgentに設定する
$res = $ua->request ($req) or die 'Failed to request';
my $sl = $res->status_line;
$log .= "mastdon status_line is $sl.";
};
if($@){ chomp($@);$log .= $@;}
else{ $log .= "toot $c_text.";}
return ($log);
}
### POST するデータ文字列を生成する
sub _get_content {
my (%param) = @_;
join '&', map {
sprintf '%s=%s', $_, $param{$_};
} keys %param;
}
### 文字列を URL エンコードする
sub _encode_url {
my $str = encode('UTF-8',shift);
$str =~ s!([^a-zA-Z0-9_.~-])!sprintf '%%%02X',ord($1)!ge;
return ($str);
}
MapBox Markerにclickイベント
GoogleMapでは、Markerにclickイベントを定義するときは、
const marker = new google.maps.Marker({
position: marker_loc,
map: gmap,
title: '赤ピン'
});
//マーカークリックでclickEventを実行
google.maps.event.addListener(marker, 'click', clickEvent);
でできるけど、MapBoxでは、
const marker = new mapboxgl.Marker({
color: '#ff0000'
})
.setLngLat(marker_loc)
.addTo(gmap);
//マーカークリックでclickEventを実行
marker.on('click', clickEvent);
では、動かん!!!
MapBoxのDocumentをよく読むと、MarkerのEventsは、「drag」「dragstart」「dragend」の3つしかないことがわかった。
Markerのソースコードを確認したところ、「click」は、MarkerのPopup機能に使われているため、それ以外では使えなくなっていることがわかった。(以下のリンクの399行目からの_onMapClick)
そこで、Markerクラスをオーバーライドして、_onMapClickを書き換えることとした。
class customMarker extends mapboxgl.Marker{
_onMapClick(e) {
const targetElement = e.originalEvent.target;
const element = this._element;
if (targetElement === element || element.contains((targetElement))) {
clickEvent();
}
}
}
const marker = new customMarker({
color: '#ff0000'
})
.setLngLat(marker_loc)
.addTo(gmap);
で、MapBoxのMarkerに、clickイベントを実行できるようにできた。
Swift CSV作成
Swiftで、Arrayから、CSV形式のStringを生成するコードを作成しました。
使い方は、headerに行の項目名などをCSV形式で指定し、arrayにCSVにしたいデータを指定して、コールすると返り値がCSV形式のStringとなります。
ただし、Swiftは内部的に文字コードはUnicodeなので、出力するときは、dataUsingEncoding(NSShiftJISStringEncoding)のメソッドで、S-JISに変換してください。
struct MakeCSV { static func MakeCSV(header:String, array:AnyObject, footer:String)->String{ var csv:String = "" let maxindex1st = array.count-1 for(var i=0;i<=maxindex1st;i++){ let row = array[i] var rowcsv :String = "" let maxindex2nd = row.count-1 for(var j=0;j<=maxindex2nd;j++){ if j != 0 {rowcsv = rowcsv + ","}//2列目以降は、先頭にコンマをつける var str:String = "" if(row[j] is String || row[j] is NSString){ //文字列の場合 //改行コードをCR+LFに str = (row[j] as! String).stringByReplacingOccurrencesOfString("\r\n", withString:"\n", options: NSStringCompareOptions.RegularExpressionSearch, range: nil) str = str.stringByReplacingOccurrencesOfString("[\r\n]", withString:"\r\n", options: NSStringCompareOptions.RegularExpressionSearch, range: nil) //ダブルコーテーションをエスケープ str = str.stringByReplacingOccurrencesOfString("\"", withString:"\"\"", options: NSStringCompareOptions.RegularExpressionSearch, range: nil) }else if(row[j] is NSDate){ //日付の場合 let dateFormatter = NSDateFormatter() dateFormatter.locale = NSLocale(localeIdentifier: "ja_JP") dateFormatter.timeStyle = .MediumStyle dateFormatter.dateStyle = .MediumStyle str = dateFormatter.stringFromDate(row[j] as! NSDate) as String }else if(row[j] is NSNumber){ //NSNumberの場合 str = (row[j] as! NSNumber).stringValue } if str != "" { rowcsv = rowcsv + "\"" + str + "\"" } } csv = csv + rowcsv + "\r\n" } return header + "\r\n" + csv + footer } }
iOSはenableHighAccuracy:trueはお勧めしない
iOS端末で、navigator.geolocation.getCurrentPositionのオプションで、enableHighAccuracyをtrueにすると、環境によっては、なかなかcallbackされないことがありました。enableHighAccuracy:trueはお勧めしません。
//ユーザーの現在の位置情報を取得(位置情報の精度を高くする)
navigator.geolocation.getCurrentPosition(
successCallback, errorCallback, {enableHighAccuracy:true;}
);
Active Perl 5.8にCrypt::SSLeayインストールするときの注意事項メモ
WindowsからCrypt::SSLeayのインストール・詳細版 - 基本は根性ナシな日記にActive PerlへのCrypt::SSLeayのインストール方法が記載されていますが、これだけでは、うまくいきません。
IISからCGIでCrypt::SSLeayを利用する場合は、ssleay32.dll/libleay32.dllのアクセス権限を、IISからアクセスできるようにする必要があります。
- コマンドプロンプトから、「ppm install http://theoryx5.uwinnipeg.ca/ppms/Crypt-SSLeay.ppd」を実行する。
- ssleay32.dllとlibleay32.dllをFetchが始まるので、PATHが通っているディレクトリにこれらのファイルを保存する。
- ssleay32.dllとlibleay32.dllのアクセス権限に<EVERYONE>もしくは<IUSER_マシン名>から、実行できるようにプロパティを変更する。
TwitterAPI1.1 https対応の自動ツイートコード
ツイッターAPI1.1が、突然httpsにのみしか応答しなくなったので、Perlでのツイート用コードを修正しました。REQUEST_URIを変えただけです。
ただし、LWP::UserAgentモジュールがhttpsに対応するためには、Crypt::SSLeayか、Net::SSLeayモジュールが必要です。(Active Perl5.8へのCrypt::SSLeayのインストールは結構手間取りました。このことは、後日、記事に書きます。)
http://www.magicvox.net/archive/2010/12311401/のソースを一部改変しています。
use uft8;
use strict; use warnings; use Encode qw(encode decode); use MIME::Base64; use LWP::UserAgent; use Digest::HMAC_SHA1; use HTTP::Request; use constant { ##Twitter REQUEST_METHOD => 'POST', REQUEST_URI => 'https://api.twitter.com/1.1/statuses/update.json', CONSUMER_KEY => 'コンシューマーキー', CONSUMER_SECRET => 'コンシューマーシークレット', ACCESS_TOKEN => 'アクセストークン', ACCESS_SECRET => 'アクセスシークレット', }; ########################################### ## 以下のソースコードは ## http://www.magicvox.net/archive/2010/12311401/ ## 掲載のものを一部修正したもの。 ## MITライセンス sub tweetMessage{ my $message = shift; my $lat = shift; my $lon = shift; my $log = ''; #文字数チェックと改行除く。 my $c_text = $message; $c_text =~ s/\r//sig; $c_text =~ s/\n//sig; my $length = length($c_text); if($length > 200){ return("Message length[$length] too much at tweetMessage.[$c_text]\n"); } ### リクエストパラメータの準備 my %oauth = ( oauth_consumer_key => CONSUMER_KEY, oauth_token => ACCESS_TOKEN, oauth_signature_method => 'HMAC-SHA1', oauth_timestamp => time, oauth_nonce => 'SD'.sprintf("%09d",int(rand(100000000))), oauth_version => '1.0'); my %param = (status => _encode_url($c_text)); if($lat && $lon){$param{lat} = $lat; $param{long} = $lon;} $oauth{oauth_signature} = get_signature( REQUEST_METHOD, REQUEST_URI, %oauth, %param); my $res; eval{ ### リクエストを生成する my $content = get_content(%param); my $req = HTTP::Request->new (REQUEST_METHOD, REQUEST_URI, ['Authorization' => get_oauth_header(%oauth), 'Content-Type' => 'application/x-www-form-urlencoded'], $content) or die 'Failed to initialize HTTP::Request'; ### リクエストを投げる if(not(F_DEBUG)){ my $ua = LWP::UserAgent->new or die 'Failed to initialize LWP::UserAgent'; $res = $ua->request ($req) or die 'Failed to request'; my $sl = $res->status_line; $log .= &addLog("twitter status_line is $sl."); } }; if($@){ $log .= &addLog("$@");} else{ $log .= &addLog("tweet『$c_text』with lat:$lat, lng:$lon.");} return ($log); } ### 送信するパラメータから oauth_signature を求める sub get_signature { my ($method, $uri, %params) = @_; my $param = join '&', map { join '=', $_, $params{$_}; } sort keys %params; my $base_string = join '&', $method, _encode_url ($uri), _encode_url ($param); my $key = join '&', CONSUMER_SECRET, ACCESS_SECRET; _encode_base64 (Digest::HMAC_SHA1::hmac_sha1 ($base_string, $key)); } ### OAuth HTTP ヘッダ文字列の生成する sub get_oauth_header { my (%param) = @_; 'OAuth '. join ', ', map { sprintf '%s="%s"', $_, _encode_url ($param{$_}); } keys %param; } ### POST するデータ文字列を生成する sub get_content { my (%param) = @_; join '&', map { sprintf '%s=%s', $_, $param{$_}; } keys %param; } ### 文字列を URL エンコードする sub _encode_url { my $str = encode('UTF-8',shift); $str =~ s!([^a-zA-Z0-9_.~-])!sprintf '%%%02X',ord($1)!ge; return ($str); } ### データを MIME::Base64 エンコードする sub _encode_base64 { my $str = shift; $str = MIME::Base64::encode ($str); # エンコードされた文字列の末尾に改行コードがくっついてくるっぽい $str =~ s/^s+|s+$//g; return ($str); ########################################### ## 以下のソースコードは ## http://www.magicvox.net/archive/2010/12311401/ ## 掲載のものを一部修正したもの。 ## MITライセンス sub tweetMessage{ my $message = shift; my $lat = shift; my $lon = shift; my $log = ''; #文字数チェックと改行除く。 my $c_text = $message; $c_text =~ s/\r//sig; $c_text =~ s/\n//sig; my $length = length($c_text); if($length > 200){ return("Message length[$length] too much at tweetMessage.[$c_text]\n"); } ### リクエストパラメータの準備 my %oauth = ( oauth_consumer_key => CONSUMER_KEY, oauth_token => ACCESS_TOKEN, oauth_signature_method => 'HMAC-SHA1', oauth_timestamp => time, oauth_nonce => 'SD'.sprintf("%09d",int(rand(100000000))), oauth_version => '1.0'); my %param = (status => _encode_url($c_text)); if($lat && $lon){$param{lat} = $lat; $param{long} = $lon;} $oauth{oauth_signature} = get_signature( REQUEST_METHOD, REQUEST_URI, %oauth, %param); my $res; eval{ ### リクエストを生成する my $content = get_content(%param); my $req = HTTP::Request->new (REQUEST_METHOD, REQUEST_URI, ['Authorization' => get_oauth_header(%oauth), 'Content-Type' => 'application/x-www-form-urlencoded'], $content) or die 'Failed to initialize HTTP::Request'; ### リクエストを投げる if(not(F_DEBUG)){ my $ua = LWP::UserAgent->new or die 'Failed to initialize LWP::UserAgent'; $res = $ua->request ($req) or die 'Failed to request'; my $sl = $res->status_line; $log .= &addLog("twitter status_line is $sl."); } }; if($@){ $log .= &addLog("$@");} else{ $log .= &addLog("tweet『$c_text』with lat:$lat, lng:$lon.");} return ($log); } ### 送信するパラメータから oauth_signature を求める sub get_signature { my ($method, $uri, %params) = @_; my $param = join '&', map { join '=', $_, $params{$_}; } sort keys %params; my $base_string = join '&', $method, _encode_url ($uri), _encode_url ($param); my $key = join '&', CONSUMER_SECRET, ACCESS_SECRET; _encode_base64 (Digest::HMAC_SHA1::hmac_sha1 ($base_string, $key)); } ### OAuth HTTP ヘッダ文字列の生成する sub get_oauth_header { my (%param) = @_; 'OAuth '. join ', ', map { sprintf '%s="%s"', $_, _encode_url ($param{$_}); } keys %param; } ### POST するデータ文字列を生成する sub get_content { my (%param) = @_; join '&', map { sprintf '%s=%s', $_, $param{$_}; } keys %param; } ### 文字列を URL エンコードする sub _encode_url { my $str = encode('UTF-8',shift); $str =~ s!([^a-zA-Z0-9_.~-])!sprintf '%%%02X',ord($1)!ge; return ($str); } ### データを MIME::Base64 エンコードする sub _encode_base64 { my $str = shift; $str = MIME::Base64::encode ($str); # エンコードされた文字列の末尾に改行コードがくっついてくるっぽい $str =~ s/^s+|s+$//g; return ($str);
}
EXIFのタグをJavascriptで読む
MOON GIFTに記載されているexif.jsでは、リトルエンディアンの場合、バグがありましたので、修正しました。
//MOON GIFT(http://www.moongift.jp/2012/05/20120502-2/)を一部改変
Exif = {};
(function () {
var MARKER_SOI = 0xffd8;
var MARKER_APP1 = 0xffe1;
var FMT_SIZE = [
1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8
];
function BinaryReader(arr, endian) {
this.arr = new Uint8Array(arr);
this.endian = endian || 0;
this.cur = 0;
this._stack = ;
}
BinaryReader.LITTLE_ENDIAN = 0;
BinaryReader.BIG_ENDIAN = 1;
BinaryReader.SEEK_ABSOLUTELY = 0;
BinaryReader.SEEK_RELATIVELY = 1;
BinaryReader._makeReadMethod = function (size) {
return function (endian) {
var target;
typeof endian === "undefined" && (endian = this.endian);
target = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return BinaryReader.pack(target, endian);
};
};
BinaryReader.pack = function (arr, endian) {
var f = endian === BinaryReader.LITTLE_ENDIAN
? Array.prototype.reduceRight // Little Endian
: Array.prototype.reduce; // Big Endian
return f.call(arr, function (a, b) {
return (a << 8) + b;
}, 0);
};
BinaryReader.pack_signed = function (arr, endian) {
var n = BinaryReader.pack(arr, endian);
if (n & (1 << arr.length * 8 - 1)) {
n = n - (Math.pow(2, arr.length * 8));
}
return n;
}
BinaryReader.packstr = function (arr) {
var res = "";
for (var i=0; i<arr.length; i++)
res += String.fromCharCode(arr[i]);
return res;
//return Array.prototype.map.call(arr, String.fromCharCode).join("");
};
BinaryReader.prototype = {
constructor : BinaryReader,
setEndian : function (endian) {
this.endian = endian;
},
byte : BinaryReader._makeReadMethod(2),
word : BinaryReader._makeReadMethod(4),
dword : BinaryReader._makeReadMethod(8),
skip : function (size) {
this.cur += size;
return this;
},
str : function (size) {
var arr = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return BinaryReader.packstr(arr);
},
getarr : function (size) {
var ret = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return ret;
},
seek : function (offset, type) {
if (type === BinaryReader.SEEK_RELATIVELY) {
this.cur += offset;
} else {
//default
this.cur = offset;
}
return this;
},
push : function () {
this._stack.push(this.cur);
return this;
},
pop : function () {
this.cur = this._stack.pop();
return this;
}
};
Exif.loadFromArrayBuffer = function (arr) {
var soi, app1 = {}, ifd0, subifd, gpsifd, base, endian;
var reader = new BinaryReader(arr, BinaryReader.BIG_ENDIAN);
//check magic byte
soi = reader.byte();
if (soi !== MARKER_SOI) {
throw "Not JPG file format";
}
//check if file format is Exif
while(1){
app1.marker = reader.byte();
app1.size = reader.byte();
if (app1.marker == MARKER_APP1) {
break;
}else if(app1.marker == 0xfffe || !app1.marker){
throw "Not Exif file format";
}
reader.skip(app1.size-2);
}
app1.exifHeader = reader.str(4);
base = reader.skip(2).cur;
app1.endian = reader.byte();
if (app1.endian === 0x4949) {
reader.setEndian(BinaryReader.LITTLE_ENDIAN);
endian = BinaryReader.LITTLE_ENDIAN;
} else {
endian = BinaryReader.BIG_ENDIAN;
}
app1.ifd0Offset = reader.skip(2).word();
//load IFD0
reader.seek(base + app1.ifd0Offset);
ifd0 = Exif.nameIfd(loadIfd(endian), Exif.IFD0_TABLE);
reader.seek(base + ifd0.exifIFDPointer);
subifd = Exif.nameIfd(loadIfd(endian), Exif.SUBIFD_TABLE);
if ("gpsInfoIFDPointer" in ifd0) {
reader.seek(base + ifd0.gpsInfoIFDPointer);
gpsifd = Exif.nameIfd(loadIfd(endian), Exif.GPSIFD_TABLE);
}
return {
ifd0 : ifd0,
subifd : subifd,
gpsifd : gpsifd
};
function loadIfd(endian) {
var ifd = {}, entries = , i, j, size, bytesLen, data;
ifd.entryLength = reader.byte();
ifd.entries = entries;
for (i = 0; i < ifd.entryLength; i++) {
entries[i] = {};
entries[i].tag = reader.byte();
entries[i].fmt = reader.byte();
entries[i].len = reader.word();
size = FMT_SIZE[entries[i].fmt - 1];
bytesLen = size * entries[i].len;
if (bytesLen <= 4) {
data = reader.getarr(4);
} else {
data = reader.word();
reader.push();
reader.seek(base + data);
data = reader.getarr(bytesLen);
reader.pop();
}
switch (entries[i].fmt) {
case 1: //unsigned byte
case 3: //unsigned short
case 4: //unsigned long
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] = BinaryReader.pack(data.subarray(j * size, (j + 1) * size), endian);
}
entries[i].len === 1 && (entries[i].data = entries[i].data[0]);
break;
case 6: //signed byte
case 8: //signed short
case 9: //signed long
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] = BinaryReader.pack_signed(data.subarray(j * size, (j + 1) * size), endian);
}
entries[i].len === 1 && (entries[i].data = entries[i].data[0]);
break;
case 2: //ascii string
entries[i].data = BinaryReader.packstr(data);
break;
case 5: //unsigned raditional
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] =
BinaryReader.pack(data.subarray(j * 8 , j * 8 + 4), endian) /
BinaryReader.pack(data.subarray(j * 8 + 4, j * 8 + 8), endian)
;
}
break;
case 10: //signed raditional
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] =
BinaryReader.pack(data.subarray(j * 8 , j * 8 + 4), endian) /
BinaryReader.pack(data.subarray(j * 8 + 4, j * 8 + 8), endian)
;
}
break;
default:
entries[i].data = data;
}
}
ifd.next = reader.word();
return ifd;
}
};
Exif.nameIfd = function (ifd, table) {
var nifd = {}, entries = ifd.entries, name;
for (var i = 0; i < ifd.entryLength; i++) {
name = table[entries[i].tag];
nifd[name || entries[i].tag] = ifd.entries[i].data;
}
return nifd;
};
Exif.IFD0_TABLE = {
256 : "mageWidth",
257 : "imageLength",
258 : "bitsPerSample",
259 : "compression",
262 : "photometricInterpretation",
274 : "orientation",
277 : "samplesPerPixel",
284 : "planarConfiguration",
530 : "yCbCrSubSampling",
531 : "yCbCrPositioning",
282 : "xResolution",
283 : "yResolution",
296 : "resolutionUnit",
273 : "stripOffsets",
278 : "rowsPerStrip",
279 : "stripByteCounts",
513 : "jPEGInterchangeFormat",
514 : "jPEGInterchangeFormatLength",
301 : "transferFunction",
318 : "whitePoint",
319 : "primaryChromaticities",
529 : "yCbCrCoefficients",
532 : "referenceBlackWhite",
306 : "dateTime",
270 : "imageDescription",
271 : "make",
272 : "model",
305 : "software",
315 : "artist",
3432 : "copyright",
34665 : "exifIFDPointer",
34853 : "gpsInfoIFDPointer"
};
Exif.SUBIFD_TABLE = {
36864 : "exifVersion",
40960 : "flashPixVersion",
40961 : "colorSpace",
37121 : "componentsConfiguration",
37122 : "compressedBitsPerPixel",
40962 : "pixelXDimension",
40963 : "pixelYDimension",
37500 : "makerNote",
37510 : "userComment",
40964 : "relatedSoundFile",
36867 : "dateTimeOriginal",
36868 : "dateTimeDigitized",
37520 : "subSecTime",
37521 : "subSecTimeOriginal",
37522 : "subSecTimeDigitized",
33434 : "exposureTime",
33437 : "fNumber",
34850 : "exposureProgram",
34852 : "spectralSensitivity",
34855 : "iSOSpeedRatings",
34856 : "oECF",
37377 : "shutterSpeedValue",
37378 : "apertureValue",
37379 : "brightnessValue",
37380 : "exposureBiasValue",
37381 : "maxApertureValue",
37382 : "subjectDistance",
37383 : "meteringMode",
37384 : "lightSource",
37385 : "flash",
37386 : "focalLength",
41483 : "flashEnergy",
41484 : "spatialFrequencyResponse",
41486 : "focalPlaneXResolution",
41487 : "focalPlaneYResolution",
41488 : "focalPlaneResolutionUnit",
41492 : "subjectLocation",
41493 : "exposureIndex",
41495 : "sensingMethod",
41728 : "fileSource",
41729 : "sceneType",
41730 : "cFAPattern",
40965 : "interoperabilityIFDPointer"
};
Exif.GPSIFD_TABLE = {
0 : "versionID",
1 : "latitudeRef",
2 : "latitude",
3 : "longitudeRef",
4 : "longitude",
5 : "altitudeRef",
6 : "altitude",
7 : "timeStamp",
8 : "satellites",
9 : "status",
10 : "measureMode",
11 : "dOP",
12 : "speedRef",
13 : "speed",
14 : "trackRef",
15 : "track",
16 : "imgDirectionRef",
17 : "imgDirection",
18 : "mapDatum",
19 : "destLatitudeRef",
20 : "destLatitude",
21 : "destLongitudeRef",
22 : "destLongitude",
23 : "bearingRef",
24 : "bearing",
25 : "destDistanceRef",
26 : "destDistance"
};
})();