Monthly Archives: 6月 2020

2020-06-24 , ,

月は出ているか? Emacs(calendar,org-mode)で月の出・月の入りの時刻を表示する

前回の続き。無事に主要な四月相(新月、上弦の月、満月、下弦の月)の日時を表示できましたが月の出や月の入り、月齢を表示する機能がありません。

月の出・月の入りなんてどうでもいいと思われる方も多いでしょうが、月が出ていない夜は サテライトキャノンが撃てません 月明かりに邪魔されず星がよく見えます。天の川など綺麗な星を見るというのは私の旅行の目的の一つになっていて、特に山へ行く計画を立てるときには必ずチェックしています。月が出ていても時間帯が昼間までならば問題なく星は見られます。カレンダーや予定表を見たときに夜月が明るくない日が分かるとその日に旅行をスケジュールしたくなるわけです。まぁ、月が出ていてもそれはそれで綺麗なのですけれど。いずれにせよ事前に分かっているに越したことはないわけです。

Emacsで日の出日の入りや月の朔望の計算はあるのですから既にありそうなものですが少し探しただけでは見つかりませんでした。

というわけで、作ったのがこちら。

使い方

calendarと連携させるには、例えば次のコードをinit.el(.emacs)に追加します(Mキーはlunar.elで使っているのでLキーにしました)。

(with-eval-after-load "calendar"
  (require 'moonrise)
  (define-key calendar-mode-map "Ld" 'calendar-moonrise-moonset)
  (define-key calendar-mode-map "Lm" 'calendar-moonrise-moonset-month))

L d でカーソル位置の日の時刻をミニバッファに表示し

ミニバッファに月の出、南中、月の入の時刻、月齢を表示
図1: ミニバッファに月の出、南中、月の入の時刻、月齢を表示

L m で月間の時刻を表示します。

一ヶ月分の月の出、南中、月の入の時刻、月齢をまとめて表示
図2: 一ヶ月分の月の出、南中、月の入の時刻、月齢をまとめて表示

また、org-modeのアジェンダに表示するには次の文をアジェンダ表示対象のファイルに追加すればOKです(新しいorgファイルを作った方がいいかも?)。

#+CATEGORY: Astro
* Moonrise
%%(moonrise-org-agenda)
org-modeのagendaに月の出、月の入の時刻を表示
図1: org-modeのagendaに月の出、月の入の時刻を表示

実装にあたって

かなり昔に買ってあった日の出・日の入りの計算―天体の出没時刻の求め方(長沢 工. 1999)という本を元に作成しました。月の位置の計算方法は「海上保安庁が開発した月位置計算の略算式に対し, 時刻原点を変更するなど, 私が多少改変した式」(長沢1999. p124)だそうです。もう20年以上前の本なので精度が気になりましたが海上保安庁水路部の略算式 - 月の位置の略算(1) 黄経: セッピーナの趣味の天文計算によれば今回のような用途に使う分にはまったく問題ない精度のようです。(私も本からの入力に難儀しました。すでに自炊した本だったのでOCRがかかっていたのですが表の部分は全然ダメ。手入力した後音声読み上げさせて目と耳で確認しました)

solar.elとかなりの部分を共通化できるのではないかと思ったのですが、太陽は黄緯が0なので黄経だけ求めればよく、また、時刻の扱い方や計算方法など色々異なっていたためほとんど共通で使えるものはありませんでした。使えたのは、恒星時の計算(solar-sidereal-time)と自転遅れの補正値(solar-ephemeris-correction)、時刻の文字列化(solar-time-string)、それと現在位置を設定する仕組み(solar-setup, calendar-longitude, calendar-latitude, calendar-time-zone)あたりでした。

課題

  • 計算結果のキャッシュ
  • おそらく正しく動作しない地点がある(極地など)

私のマシンではそれほど遅くないのですが、計算結果をキャッシュすると待たされる時間が減るかもしれません。とはいえagendaの生成自体が元々遅いのでどこまで意味があるのか分かりませんが。

極地など地球上のどこかの地点では正しく動作しないと思われます。対処法は本にも書いてあるのですが、面倒なのであまり読んでませんすみません。

(2021-07-25追記)月の満ち欠けを画像表示させてみました

2020-06-21 , ,

月相をEmacsのカレンダーに追加する

きっかけは夏至。今日は夏至かぁ……と思ってorg-modeのアジェンダやcalendarを見ても夏至(Summer Solstice)が表示されていません。Emacsでは標準で表示するはずなのに。調べてみたところ、japanese-holidays.el 用の設定で変数 calendar-holidays の値を日本の休日で完全に置き換えてしまっていました。その際に元々あった (solar-equinoxes-solstices) が消えていたんですね。 calendar-holidays(solar-equinoxes-solstices) を追加したら表示されるようになりました。ついでに英語だとなんだかしっくりこないので solar-n-hemi-seasons("春分" "夏至" "秋分" "冬至") に修正しました。

ついでなので月相も追加してしまおうと思いました。変数 calendar-holidays の説明に次のように書いてあったからです。

To include the phases of the moon, add

(lunar-phases)

to the holiday list, …

M-x describe-function lunar-phases してみるとそういう名前の関数はあるみたいです。なので calendar-holidays(lunar-phases) を追加して M-x calendar してみたところエラー(Wrong type argument: listp, 109)と共に直近の主要な月相一覧が表示されました。 lunar-phases を詳しく調べてみるとこの関数は単に新月、上弦の月、満月、下弦の月の日時を三ヶ月分表示するものだったようです。

再度変数 calendar-holidays の説明をよく見ると続きがあって

To include the phases of the moon, add

(lunar-phases)

to the holiday list, where ‘lunar-phases’ is an Emacs-Lisp function that you’ve written to return a (possibly empty) list of the relevant VISIBLE dates with descriptive strings such as

(((2 6 1989) "New Moon") ((2 12 1989) "First Quarter Moon") … ).

と書いてありました。要するに lunar-phases というのはあくまで例として挙げた架空の関数で、あなたが書いてくださいよ、ということのようです。既にないか探したのですが見つからなかったので仕方なく自分で書くことにしました。

(require 'lunar)
(require 'cl)

(defun my-lunar-phases-for-calendar-holidays ()
  (let ((m displayed-month)
        (y displayed-year))
    (calendar-increment-month m y -1)
    (loop for x in (lunar-phase-list m y)
          collect (list
                   (nth 0 x)
                   (concat
                    (lunar-phase-name (nth 2 x))
                    " "
                    (nth 1 x))))))

(lunar-phase-list) がほぼそのまま使えるかと期待したのですが微妙に形式が違うので変換する必要がありました。

それで、この関数を指定するリスト '(my-lunar-phases-for-calendar-holidays) を変数 calendar-holidays に追加します。 japanese-holidays.el を使っていたりするとすでに書き替えている場所があるかもしれないのでうまいこと追加してください。

lunar-phase-names も日本語にしてしまいましょう。 ("新月" "上弦の月" "満月" "下弦の月")

というわけでなんとか主要な月相をcalendarやorg-modeのアジェンダに表示できました。

2020-06-21-lunar-phases.png

そういえば今日は部分日食が話題になっていました。なるほど月は太陽の方向にあるから新月ですね。

それにしても何で標準で用意されていないのでしょうね。ここまで用意しておきながら。Emacs上で月相を知りたい人は居ないのかな。……居ないんでしょうね。

再度調べてみると次のような質問を見つけました。

org-agendaに直接月相を表示する方法です。calendarに入れなくてもいいならこれでも可。

ところで月の出と月の入りも表示できると便利な気もするのですが……

(2020-06-24追記):次の記事では毎日の月の出と月の入り、南中の時刻、そして毎日の月齢も実現します。

2020-06-03 ,

org-modeで幅の広い表を折り返しモード時でも折り返さず水平スクロールさせる

昨日の続き。

org-mode ではデフォルトで truncate-lines がtとなっています(org-startup-truncated で変更できます) 。つまり、折り返さずに表示するのがデフォルトです。これはおそらく折り返すと幅が広い表(テーブル)が正しく表示されないためではないでしょうか。

しかしそれだと普通のテキストも折り返さずに表示されて大変読みづらくなってしまいます。fill-paragraph 等を使って自分で適当な位置に改行を入れていくことになるのですが多少問題もあります。私はよくHTMLでエクスポートするのですが、文字と文字との間にわずかに空白が入ってしまうことがあります。確認してみるとそれは文章の途中にある改行のせいだったりします。なので私は必要に応じて M-x toggle-truncate-lines で切り替えていたのですが、やっぱり面倒くさい。

というわけで、先日作った部分的に水平スクロールできるようにするコードをorg-modeの表部分に適用してみました。

ソースコードはGitHubにあります。

misohena/phscroll: Enable partial horizontal scroll in Emacs

使い方は (load "org-phscroll.el") するだけ。スクロールは C-x < や C-x > でも可能です。

それと truncate-lines はnilにしないと意味がありません。私は org-startup-truncated をnilに変更してしまいました。

表全体を消した場合自動的にスクロールエリアも削除されますが、表が表で無くなった場合(先頭の|を消すなどして)、スクロールエリアが残ってしまいます。 M-x phscroll-delete-at でポイントがある位置のスクロールエリアを削除できます。

org-phscroll.elの使用例
図1: org-phscroll.elの使用例

実装にあたっては、例によってfont-lockのタイミングで表を探して水平スクロール領域を適用しています。他のタイミングでも良いのですがfont-lock処理の直後、特にorg-fontify-meta-lines-and-blocksの直後ならばfaceがorg-tableかどうかで表かどうかを判別できるのでそこにしました。単純に行頭に|があるかどうかで判別すると、各種ブロック(#+BEGIN_~#+END_)の中にあるものまで拾ってしまうので。

phscroll.el自体の問題も色々修正したのでそこそこ使えるようになってきましたが、大きな表は少し重いかもしれません。できるだけウィンドウ内だけ更新するように作ってありますが、折りたたんだ場所に沢山の表が入っている場所がウィンドウ内に入ると、それらの表を全て一度に整形することになるので時間がかかります。

今のところオーバーレイやテキストプロパティを一文字毎にチェックしているので、それをある程度まとめてやれば少し速くなるかもしれません。面倒くさいですけど。

2020-06-02

Emacsで部分的な水平スクロールを実現する

Emacsでは toggle-truncate-lines を使えばバッファ全体の水平スクロールが可能です。行の折り返しをやめて左右端で切り詰めて表示できるので、幅の長い整形済みテキストを折り返さずに見たい場合には有効です。

しかしこのモードを使うと当然ながら文書全体で折り返しが無くなってしまいます。文書内の通常の文章は折り返して表示し、整形済みの部分だけ水平スクロールを適用する、そんな器用な機能は見当たりませんでした。

一方Emacsにはテキストプロパティオーバーレイという機能でテキストの一部を非表示にできます。となると、行の先頭部分を非表示にすることで擬似的に部分的な水平スクロールを実現できないでしょうか。

というわけで実装したのがこちら:

misohena/phscroll: Enable partial horizontal scroll in Emacs

使い方は

  1. elispをロードする 例:(require 'phscroll)
  2. スクロールさせたいリージョンで囲む
  3. M-x phscroll-region

これで選択した部分だけ水平スクロール出来るようになります。

ただし、truncate-lines が t (折り返さず表示するモード)のときは加工せずそのまま表示します。Emacs標準の水平スクロール機能を使用してください。 M-x toggle-truncate-lines で切り替えると、それを検出して表示を更新します。

既知の問題:

  • 遅い : すでに適用されているテキストプロパティやオーバーレイを調べて各行の幅を計算する部分やオーバーレイを適用して一部を非表示にする部分がかなり遅いみたいです。頑張って必要な部分だけ(windowで表示する範囲内や編集した部分だけ)更新するようにしてみましたが、それでもまだ遅いみたいです。
  • 複数ウィンドウ対応 : 複数のウィンドウで水平スクロール部分を見たときに表示が乱れることがあります。特にフレームを左右に分割したときに左右の幅が異なる場合は選択している方のウィンドウ幅でレイアウト計算するので、他のウィンドウではウィンドウの幅を超えてしまうことがあります。
  • display, invisibleプロパティの対応形式: 既にdisplayプロパティinvisibleプロパティで加工されているテキストも水平スクロールの対象にできますが、正しく幅を計算できる形式は限られています。単純に文字列に置き換えている場合やただ消している場合、それとrelative-spaceは対応しましたがそれ以外は未対応です。
phscroll.el使用例
図1: phscroll.el使用例

これだけだといちいちスクロールさせるエリアを手動で設定しなければならず面倒なので、次回は自動的にエリアを設定する方法について書こうと思います。

ふと思ったのですが、これとは逆に通常部分のみ折り返すというのは可能なのでしょうか。truncate-lines tで通常部分だけオーバーレイで折り返す。オーバーレイで改行ってできるのかな。でも変更があるたびに改行位置を維持するのは大変そうですね。