Archive for the 'CLLocation' Category

2015/09/25 Remindersのイベントに場所(geofence alarm)を追加する(OS X 10.11版)

Reminders.app(日本語名:リマインダー)の指定イベントに場所(geofence alarm)を追加するAppleScriptのEl Capitan(OS X 10.11)版です。

Yosemite(OS X 10.10.x)のScript Editor上では実行はおろか構文確認(コンパイル)もできないのでご注意ください。

El Capitanのリリースノートで「バグ修正」項目として紹介されている内容の1つで・・・Cocoaの各APIでの定数をAppleScriptにBridgeするようになった、という項目のテスト用にOS X 10.11上で一部修正してみました。

OS X 10.10.x上では、こうした定数があったら、

enums.png

最初の項目が0で、次が1で・・・とあたりをつけて数値で指定するとか、Xcode上でヘッダーファイルを確認して実際の値を調べる・・・という不毛な作業が必要だったわけですが、それがEl Capitanでは解消されるということです。

ただ、目下AppleScriptのプログラムリストの中で、「変数およびサブルーチン名」として色分けされる項目が、とくにCocoaの機能を使うようになってからというもの増えすぎており、可読性が落ちているので、なんとかしてほしいところです。

ちなみに、ASObjC Explorer 4ではこの問題を軽減しており、「サブルーチン名」と「Cocoaのメソッド名」については変数名とは色分けするようになっています(El Capitan上で比較のためにスクリーンショットを撮りましたが、掲載してはいけないことに気づいて削除)。

AppleScript名:Remindersのイベントに場所(geofence alarm)を追加する_10.11
– Created 2014-12-08 by Shane Stanley
– Modified 2015-09-24 by Takaaki Naganoya –Geofence Alarm
– Modified 2015-09-24 by Shane Stanley –Fix the way to Create the geofence alarm
– Modified 2015-09-24 by Takaaki Naganoya –change and test for El Capitan’s Enum bridging

–Reference:
–http://stackoverflow.com/questions/26903847/add-location-to-ekevent-ios-calendar
–http://timhibbard.com/blog/2013/01/03/how-to-create-remove-and-manage-geofence-reminders-in-ios-programmatically-with-xcode/

use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use framework “EventKit”

–Fetch an event to add geofence alarm
tell application “Reminders”
  tell account “iCloud”
    tell list “ホーム” –Use “Home” in English environment or your favorite one
      set theID to id of (last reminder whose completed is false)
    end tell
  end tell
end tell

set theID to text 20 thru -1 of theID

setLocationToLeaveForReminderID(35.745769, 139.675565, “ゲームシティ板橋”, theID, 200)

–指定場所に到着した時に発動するGeofence Alarmを指定のリマインダーに登録する。Geofence半径指定つき(単位:メートル)
on setLocationToEnterForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:(current application’s EKEntityMaskReminder)
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0
  
enterAlarm’s setProximity:(current application’s EKAlarmProximityEnter)
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToEnterForReminderID

–指定場所を出発した時に発動するGeofence Alarmを指定のリマインダーに登録する。Geofence半径指定つき(単位:メートル)
on setLocationToLeaveForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:(current application’s EKEntityMaskReminder)
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
–Create the geofence alarm
  
set leaveAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0
  
leaveAlarm’s setProximity:(current application’s EKAlarmProximityLeave)
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter
  
leaveAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:leaveAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToLeaveForReminderID

★Click Here to Open This Script 

2015/09/24 Remindersのイベントに場所(geofence alarm)を追加する

Reminders.app(日本語名:リマインダー)の指定イベントに場所(geofence alarm)を追加するAppleScriptです。

リマインダーのAppleScript用語辞書では「場所」についてはまったくサポート機能が用意されておらず(El CapitanのRemindersでもサポートなし)、全世界のScripterが肩を落としたものでした(Apple純正アプリのAppleScript用語辞書の残念さは本当に残念です。Finderのタグなど登場して相当の時間が経っているのに機能が追加されていません。タグについてはASOCで直接操作できるので、Appleの対応をもはや期待していない昨今)。

rem5.png
▲リマインダー(Reminders.app)のAppleScript用語辞書にはLocation関連の機能はない

そこで、出来のよくないリマインダーをアテにせず、Cocoaの機能を呼び出して、AppleScriptだけで「場所」の情報を追加するAppleScriptを書いてみました(Shane Stanleyの添削済み)。

この「場所」については、若干の説明が必要です。

geofence.png

緯度・経度でピンポイントの場所を指定できますが、ピンポイントすぎるのと誤差を許容するため「だいたいこのあたり」といった範囲の指定が必要になります。これを「Geofence」と呼ぶようです。

このピンポイントの場所から「半径XXXXメートル以内」のGeofenceに入った場合のアラーム(Enter Alarm)と、出た場合のアラーム(Leave Alarm)の2種類のうちどちらかを設定できるとのこと。

逆に、入るとか出るとかいうアクション指定のないものも作れますが、これがどういう挙動をするのかいまひとつ分かりません(Geofence内でずっと何かの通知が出る?)。

rem1.png
▲テスト用に作ったリマインダー項目

rem2.png
▲初期状態では何も「場所」関連の情報はついていません

rem3.png
▲本Script実行後の状態

rem4.png
▲内容を確認すると、意図したとおり「ゲームシティ板橋」のGeofenceが追加

img_2822_resized.png
▲ゲームシティ板橋店

AppleScript名:Remindersのイベントに場所(geofence alarm)を追加する
– Created 2014-12-08 by Shane Stanley
– Modified 2015-09-24 by Takaaki Naganoya
– Modified 2015-09-24 by Shane Stanley

–Reference:
–http://stackoverflow.com/questions/26903847/add-location-to-ekevent-ios-calendar
–http://timhibbard.com/blog/2013/01/03/how-to-create-remove-and-manage-geofence-reminders-in-ios-programmatically-with-xcode/

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “EventKit”

–Fetch an event to add geofence alarm
tell application “Reminders”
  tell account “iCloud”
    tell list “ホーム” –Use “Home” in English environment or your favorite one
      set theID to id of (last reminder whose completed is false)
      
–>  ”x-apple-reminder://868467FD-1F90-47EF-A9B1-A39334341D41″
    end tell
  end tell
end tell

set theID to text 20 thru -1 of theID
–> “868467FD-1F90-47EF-A9B1-A39334341D41″

setLocationToEnterForReminderID(35.745769, 139.675565, “ゲームシティ板橋”, theID, 200)

on setLocationToEnterForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:2 –Deprecated in OS X 10.9 ?
  
(* Enums: { EKEntityMaskEvent:1, EKEntityMaskReminder:2} *)
  
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0 –ここがキモ
  
  
enterAlarm’s setProximity:1 – EKAlarmProximityEnter
  
(* Enums: { EKAlarmProximityNone:0, EKAlarmProximityEnter:1, EKAlarmProximityLeave:2 } *)
  
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter –by meters
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToEnterForReminderID

on setLocationToLeaveForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:2 –Deprecated in OS X 10.9 ?
  
(* Enums: { EKEntityMaskEvent:1, EKEntityMaskReminder:2} *)
  
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0 –ここがキモ
  
  
enterAlarm’s setProximity:2 – EKAlarmProximityLeave
  
(* Enums: { EKAlarmProximityNone:0, EKAlarmProximityEnter:1, EKAlarmProximityLeave:2 } *)
  
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter –by meters
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToLeaveForReminderID

★Click Here to Open This Script 

2015/03/04 現在地点の緯度経度情報を取得する

This AppleScript get current location of your Mac by using CoreLocation. There is need to enable WiFi to get location using this AppleScript.

You can get altitude, timestamp, speed and course easily. But latitude and longitude were difficult to get.

I asked to Shane how to do this. His answer is “get description”.

You must execute this AppleScript in foreground. Original Script is made for a part of ASOC project on Xcode.

loc1.png
▲システム環境設定の「セキュリティとプライバシー」でScript EditorもしくはASObjC Explorer 4に位置情報の取得を許可しておく必要があります

loc2.png
▲許可しないとエラーが表示されます

本AppleScriptはお使いのMacの現在位置の緯度経度情報を取得するものです。CoreLocationを用いて、高度、タイムスタンプ、スピード、進行方向については容易に取得できました(位置情報を取得するためにWiFiをオンにしておく必要あり)。ただ、肝心の緯度経度情報だけはなかなか取得できず、Shaneに聞いてみたところ・・・「descriptionを取得しろ」とのこと(サンプルつきで)。

本AppleScriptの実行時には、foregroundで明示的に実行させる必要があります。オリジナルのScriptはASOC on Xcodeの一部として書いたもので、位置情報の取得/許可については本Scriptの方がうまくいっていない感じです(ASObjC Explorerがコケたりする)。

AppleScript名:GetCurrentLocation_Yosemite v3
– Created 2015-03-04 by Takaaki Naganoya, Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “CoreLocation”
use framework “AppKit”

property curLatitude : 0
property curLongitude : 0

on run
  startStandardUpdates()
end run

on startStandardUpdates()
  –Listing 1-1 Starting the standard location service
  
set locationManager to current application’s CLLocationManager’s alloc()’s init()
  
  
set locE to locationManager’s locationServicesEnabled()
  
if (locE as boolean) = true then
    locationManager’s setDelegate:me
    
locationManager’s setDesiredAccuracy:(current application’s kCLLocationAccuracyNearestTenMeters)
    
locationManager’s setDistanceFilter:500
    
locationManager’s startUpdatingLocation()
  end if
  
end startStandardUpdates

on locationManager:manager didUpdateLocations:locations
  
  
–Listing 1-3 Processing an incoming location event
  
set location to (locations’s lastObject())
  
set eventDate to (location’s timestamp())
  
set howRecent to (eventDate’s timeIntervalSinceNow())
  
  
–Calc ABS
  
set howRecent to howRecent as real
  
set howRecent to absNum(howRecent)
  
  
if howRecent < 15.0 then
    
    
set alt to location’s altitude
    
–>  (NSNumber) 46.356517791748
    
    
set aSpeed to location’s speed
    
–>  (NSNumber) -1.0
    
    
set aCourse to location’s course –North:0, East:90, South:180, West:270
    
–>  (NSNumber) -1.0
    
    
–By Shane Stanley
    
set theDescription to location’s |description|()
    
–> (NSString) “< +35.xxxxx,+139.xxxxxx> +/- 65.00m (speed -1.00 mps / course -1.00) @ 2015/03/04 8時56分41秒 日本標準時”
    
    
set anNSScanner to current application’s NSScanner’s scannerWithString:theDescription
    
anNSScanner’s setCharactersToBeSkipped:(current application’s NSCharacterSet’s characterSetWithCharactersInString:“< ,")
    
set {theResult, aLat} to anNSScanner’s scanDouble:(reference)
    
set {theResult, aLng} to anNSScanner’s scanDouble:(reference)
    
    
copy {aLat, aLng} to {my curLatitude, my curLongitude}
    
  else
    stopUpdatingLocation()
  end if
  
end locationManager:didUpdateLocations:

on locationManager:anCLLocationManager didFailWithError:anNSError
  display dialog (anNSError’s localizedDescription() as text)
end locationManager:didFailWithError:

on absNum(aNum)
  if aNum > 0 then
    return aNum
  else
    return (aNum * -1)
  end if
end absNum

★Click Here to Open This Script 

2015/03/03 2点間の距離を求める v3

Shane Stanley wrote me the easier way to get distance of two places. I think easier way is better, too.

Shane Stanleyから「もっと楽な方法があるよ」と教えてもらいました。自分も簡単な方がいいと思います(^ー^;

AppleScript名:Calc Distance between 2 places v3
– Created 2015-03-03 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “CoreLocation”

set aPlace to current application’s CLLocation’s alloc()’s initWithLatitude:35.737072 longitude:139.637826
set bPlace to current application’s CLLocation’s alloc()’s initWithLatitude:35.753108 longitude:139.595123
set distanceInMetres to aPlace’s distanceFromLocation:bPlace
–>  4252.71123319014

★Click Here to Open This Script 

2013/09/16 AppleScriptObjCで位置情報を取得

AppleScriptObjCでLocationManagerを利用して、現在位置の情報を取得するサンプルです。

あまりよく理解していないのですが、MacScripterの議論を参考に、不必要なコードを除去して最低限の機能だけ利用できればと考えて整理したものです。

こうした位置情報を取得する機能はAppleScript/AppleScriptObjCのプログラムでも、気軽に使いたいところです。

ただ、「気軽に利用できる」のと「気軽に配布できる」の間にはずいぶんと距離があります。利用するだけなら、そうした機能をAppleScriptに提供するソフトウェアが存在していますが……配布するとなると、それを一緒に配布するわけにもいきませんし、その程度の機能で「有償ソフトウェア」といわれても困ります。

位置情報の取得まわりは、かなり高機能なAPIが揃っているのが見て取れるのですが、緯度経度情報から住所情報に変換する「逆住所ジオコーダー」(reverse geocoder)がiOSにしか見当たらないため、直接GoogleのAPIを使うほかないのかと考えていますが……10.9になったらさっくりMac OS Xからも逆住所ジオコーダーを使えそうな気が……。

loc1.png
▲Xcodeプロジェクトをビルドして実行すると、位置情報を使用してよいかダイアログが表示される

loc2.png
▲緯度、経度の情報と誤差情報が表示される

loc3.png
▲「Open In Browser」ボタンをクリックすると、Google Map(日本語版)で取得位置の確認が行える

→ Xcodeプロジェクトのダウンロード(42KB)

AppleScriptObjCファイル名:coreLocationASOCAppDelegate.applescript

– coreLocationASOCAppDelegate.applescript
– coreLocationASOC

– Created by Takaaki Naganoya on 10/09/15.
– Copyright 2010 Takaaki Naganoya. All rights reserved.

–http://macscripter.net/viewtopic.php?id=32763&p=3

script coreLocationASOCAppDelegate
  
  property parent : class “NSObject”
  
  property CLLocation : class “CLLocation”
  
property CLLocationManager : class “CLLocationManager”
  
  property NSString : class “NSString”
  
property nsurl : class “NSURL”
  
property NSDate : class “NSDate”
  
property NSWorkspace : class “NSWorkspace”
  
property NSBundle : class “NSBundle”
  
  property locationManager : missing value
  
  property locationText : missing value
  
property accuracyText : missing value
  
  
  
  on applicationWillFinishLaunching_(aNotification)
    
    set locationManager to CLLocationManager’s alloc()’s init()
    
    locationManager’s setDesiredAccuracy_(current application’s kCLLocationAccuracyHundredMeters)
    
locationManager’s setDelegate_(me)
    
locationManager’s startUpdatingLocation()
    
  end applicationWillFinishLaunching_
  
  
  on locationManager_didFailWithError_(locationManager, myError)
    
    set errorString to NSString’s stringWithFormat_(“Location manager failed with error: %@”, myError’s localizedDescription())
    
webView’s mainFrame()’s loadHTMLString_baseURL_(errorString, missing value)
    
locationText’s setStringValue_(“No Data Available”)
    
accuracyText’s setStringValue_(“”)
    
  end locationManager_didFailWithError_
  
  
  on locationManager_didUpdateToLocation_fromLocation_(locationManager, newLocation, oldLocation)
    
    set latitude to newLocation’s coordinate’s pointValue()’s x
    
set longitude to newLocation’s coordinate’s pointValue()’s y
    
    locationText’s setStringValue_(NSString’s stringWithFormat_(“%@, %@”, latitude, longitude))
    
accuracyText’s setStringValue_(NSString’s stringWithFormat_(“%@”, newLocation’s horizontalAccuracy()))
    
    locationManager’s stopUpdatingLocation()
    
    
  end locationManager_didUpdateToLocation_fromLocation_
  
  
  on clicked_(sender)
    set aTag to tag of sender
    
set aTag to aTag as integer
    
    if aTag = 100 then
      tell current application to quit
    else if aTag = 200 then
      openInDefaultBrowser_(sender)
    end if
    
  end clicked_
  
  
  
  on applicationShouldTerminate_(sender)
    locationManager’s stopUpdatingLocation()
    
return current application’s NSTerminateNow
  end applicationShouldTerminate_
  
  
  
  –Webブラウザで現在の緯度、経度情報を元にGoogle Mapを表示
  
on openInDefaultBrowser_(sender) –Connected to a button in IB
    
    set currentLocation to locationManager’s location()
    
    if currentLocation is not missing value then
      set lat to currentLocation’s coordinate’s pointValue()’s x
      
set lon to currentLocation’s coordinate’s pointValue()’s y
      
set latRange to latitudeRangeForLocation_(currentLocation)
      
set lonRange to longitudeRangeForLocation_(currentLocation)
    else
      set lat to 37.76
      
set lon to -122.434
      
set latRange to 0.15
      
set lonRange to 0.15
    end if
    
    set browserURL to nsurl’s URLWithString_(NSString’s stringWithFormat_(“http://maps.google.co.jp/maps?ll=%@,%@&spn=%@,%@”, lat, lon, latRange, lonRange))
    
    NSWorkspace’s sharedWorkspace’s openURL_(browserURL)
    
  end openInDefaultBrowser_
  
  
  
  
  on latitudeRangeForLocation_(aLocation)
    
    set M to 6.367E+6 –approximate average meridional radius of curvature of earth
    
set metersToLatitude to 1.0 / ((3.1416 / 180.0) * M)
    
set accuracyToWindowScale to 2.0
    
    return (aLocation’s horizontalAccuracy()) * metersToLatitude * accuracyToWindowScale
    
  end latitudeRangeForLocation_
  
  on longitudeRangeForLocation_(aLocation)
    
    set latRange to latitudeRangeForLocation_(aLocation)
    
return latRange * (getCos_((aLocation’s coordinate’s pointValue()’s x) * 3.1416 / 180.0))
    
  end longitudeRangeForLocation_
  
  
  on getCos_(x)
    return (1 - x ^ 2 / 2 + x ^ 4 / 24 - x ^ 6 / 720 + x ^ 8 / 40320)
  end getCos_
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に