先日、Evernote Food のサポートが終了するので… - エンジニアのソフトウェア的愛情 という記事を書きました。その最後に
同じ要領で
notes[i].longitude()
,notes[i].latitude()
を使えば位置情報を取得できるので、住所を設定することもできると思います。
と無責任に書いたのですが、よくよく調べてみるとそこそこ難しい。技術的に難しいというよりも必要な情報を見つけるのが難しい。
と、いうわけで。位置情報から住所を取得するため四苦八苦した結果を記録しておきます。
なお、検索と試行錯誤を繰り返した結果でして、仕様の裏が取れているわけではありませんので、その点はご容赦を。
技術情報
緯度経度から住所を取得するには…
今回は Google Maps Geocoding API を利用しました。
クエリの latlng
に緯度経度を指定して次の URL に GET でリクエストすると JSON 形式で結果を取得することができます。
http://maps.google.com/maps/api/geocode/json?latlng=緯度,経度
また language
を指定すると、指定した言語表記で結果を取得することができます。
例)北緯24度26分16.75104秒(24.4379864度)、東経123度0分38.96604秒(123.0108239度)の住所
$ curl 'http://maps.google.com/maps/api/geocode/json?latlng=24.4379864,123.0108239&language=ja' { "results" : [ { "address_components" : [ { "long_name" : "3024", "short_name" : "3024", "types" : [ "sublocality_level_4", "sublocality", "political" ] }, { "long_name" : "与那国", "short_name" : "与那国", "types" : [ "sublocality_level_1", "sublocality", "political" ] }, { "long_name" : "与那国町", "short_name" : "与那国町", "types" : [ "locality", "political" ] }, { "long_name" : "八重山郡", "short_name" : "八重山郡", "types" : [ "colloquial_area", "locality", "political" ] }, { "long_name" : "沖縄県", "short_name" : "沖縄県", "types" : [ "administrative_area_level_1", "political" ] }, { "long_name" : "日本", "short_name" : "JP", "types" : [ "country", "political" ] }, { "long_name" : "907-1801", "short_name" : "907-1801", "types" : [ "postal_code" ] } ], "formatted_address" : "日本, 〒907-1801 沖縄県八重山郡与那国町与那国3024", "geometry" : { "location" : { "lat" : 24.4443869, "lng" : 122.9841723 }, "location_type" : "ROOFTOP", "viewport" : { "northeast" : { "lat" : 24.4457358802915, "lng" : 122.9855212802915 }, "southwest" : { "lat" : 24.44303791970849, "lng" : 122.9828233197085 } } }, "place_id" : "ChIJOZRCOXdOZzQRyEH9ltU1-b0", "types" : [ "sublocality_level_4", "sublocality", "political" ] }, ... 以下略
指定したURLからコンテンツを取得するには…
今回は Foundation Framework の各機能を利用しています。
- NSURL
- NSData
- NSString
取得の手順はこう。
- 取得する URL の
NSURL
オブジェクトを用意する NSData
のdataWithContentsOfURL:
メソッドでデータを取得するNSString
のinitWithData:encoding:
メソッドで取得したデータを文字列(NSString
オブジェクト)に変換するNSString
のcStringUsingEncoding:
メソッドで C 言語の文字列(文字配列)に変換する
これをふまえて実装してみます。
#import <Foundation/Foundation.h> #import <stdio.h> int main(int argc, char **argv) { NSURL *url = [NSURL URLWithString:@"http://maps.google.com/maps/api/geocode/json?latlng=24.4379864,123.0108239&language=ja"]; NSData *data = [NSData dataWithContentsOfURL:url]; NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; const char *cstring = [string cStringUsingEncoding:NSUTF8StringEncoding]; printf("%s\n", cstring); return 0; }
geocoding_sample.m というファイル名で保存してコンパイルして実行します。
$ clang -o geocoding_sample geocoding_sample.m -framework Foundation $ ./geocoding_sample { "results" : [ { "address_components" : [ { "long_name" : "3024", "short_name" : "3024", "types" : [ "sublocality_level_4", "sublocality", "political" ] }, { "long_name" : "与那国", "short_name" : "与那国", "types" : [ "sublocality_level_1", "sublocality", "political" ] }, ... 以下略
それを JavaScript で利用するには…
今回、緯度経度から住所を取得したいのは、Evernote を JavaScript で操作して住所を追加したいというのが動機でした。
ここまで Evernote の操作には JavaScript を利用しています。
と、いうわけで。上記の住所取得操作を JavaScript に書き直します。このあたりはかなり手探りの情報です。
osascript をインタラクティヴモードで起動しつつ話を進めます。
$ osascript -i -l JavaScript >>
>>
がプロンプトです。以下、プロンプトから始まる行はインタラクティヴモードで入力した文字列、=>
から始まる行がその実行結果になります。
>> 1 + 1 => 2 >>
クラス
NSURL
, NSData
, NSString
といったクラスはすべて $
オブジェクトの要素として参照できます。
>> $.NSURL => $.NSURL >> $.NSData => $.NSData >> $.NSString => $.NSString >> $ => [function $] { "name":"", "prototype":{"constructor":[function $]}, "NSURL":$.NSURL, "NSString":$.NSString, "NSData":$.NSData } >>
少なくとも undefined
などではなく、参照できていることがわかります。
メソッド
メソッドの呼び出しは、普通に JavaScript の構文の世界です。
オブジェクトに続いてドットでメソッド名をつけ、引数は括弧でくくります。
>> url = $.NSURL.URLWithString('http://maps.google.com/maps/api/geocode/json?latlng=24.4379864,123.0108239&language=ja') => [id NSURL] >> data = $.NSData.dataWithContentsOfURL(url) => [id _NSInlineData]
問題は initWithData:encoding:
のような複数の引数を持つ場合です。
確実な仕様を見つけられなかったのですが、このような場合はセレクタを連結してキャメルケースにしたメソッドにマップされているようです。
つまり、
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
これがこうなります。
$.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding)
なお、NSUTF8StringEncoding
のような定数も $
の要素として格納されているようです。
>> string = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding) => $("{\n \"results\" : [\n {\n \"address_components\" : [\n {\n \"long_name\" : \"3024\", ... 以下略
Evernote のノートのタイトルに住所を追加する
これらをふまえて。Evernote のノートのタイトルに住所を追加するスクリプトを書いてみます。
タイトルに単に住所を付け加える処理だけでは、繰り返し実行するたびに住所が付け加えられてしまいます。
ですので、タイトルを更新した時に更新済みタグを付けておき、検索の時にそのタグが付いているノートを対象外にするようにしました。
var getAddress = function(latitude, longitude) { var url = $.NSURL.URLWithString('http://maps.google.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude + '&language=ja'); var data = $.NSData.dataWithContentsOfURL(url); var string = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding); var cstring = string.cStringUsingEncoding($.NSUTF8StringEncoding); var json = JSON.parse(cstring) if(json.results.length > 0) { // ここでレスポンスから適当に住所を組み立ててください var long_names = []; for(var i in json.results[0].address_components) { long_names.unshift(json.results[0].address_components[i].long_name); } return long_names.join(' ') } else { return ''; } } // Evernote オブジェクトを取得する var evernote = Application('Evernote'); // food タグが付いていて、pinpointed タグが付いていないノートを検索する var notes = evernote.findNotes('tag:food -tag:pinpointed'); // pinpointed タグ オブジェクトを取得する var pinpointed_tag = evernote.tags['pinpointed'] // pinpointed タグ が存在しなければタグを作成する if( ! pinpointed_tag.exists()) { pinpointed_tag = evernote.make({new: 'tag', withProperties: {name: 'pinpointed'}}) } for(var i in notes) { var latitude = notes[i].latitude(); var longitude = notes[i].longitude(); var address = getAddress(latitude, longitude); if(address != '') { var title = notes[i].title(); console.log(title); // タイトルに住所を付け加える notes[i].title = title + ' @' + address; // pinpointed タグを付ける evernote.assign(pinpointed_tag, {to: notes[i]}); } }
適当なファイル名で保存し、osascript
コマンドで実行するとタイトルに住所が付け加えられます。
いつか読むはずっと読まない:過去からの一撃!!
「楽園通信社綺談 ビブリオテーク・リヴ」の復刻に続いて驚きの展開。「驚きの展開」とか言ったら怒られますが。
10月末発売の佐藤明機『リプライズ』(駒草出版)
これにあと、キラキラ模様のコーティングを施します。
あ、赤い線は私が引いたもので、デザインではありません。 pic.twitter.com/UL5awSZAEi
— 島田一志 (@kazzshi69) 2015, 9月 26

- 作者: 佐藤明機
- 出版社/メーカー: 駒草出版
- 発売日: 2015/10/30
- メディア: コミック
- この商品を含むブログ (3件) を見る

- 作者: 佐藤明機(さとうあきとき)
- 出版社/メーカー: 駒草出版
- 発売日: 2015/08/28
- メディア: 単行本
- この商品を含むブログ (12件) を見る

- 作者: 佐藤明機
- 出版社/メーカー: 新潮社
- 発売日: 2012/08/09
- メディア: コミック
- 購入: 10人 クリック: 200回
- この商品を含むブログ (15件) を見る