にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

気象庁の天気予報文をパースする ☀☁☂

天気予報文

気象庁の天気予報には 予報文 があります。

北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨

気象庁 | 天気予報 : 東京都 (10/20 11:00発表 21日の天気予報文)

やや難解ですが、 気象庁|予報用語 と照らし合わせることで下記であることが分かります。

時間 天気
0〜15時 くもり 時々 晴れ
15〜21時 くもり
21〜24時 くもり 所により 雨

天気予報文と正規表現

ここからは根拠のない独自研究です。

文法

気象庁が発表する天気予報文には 文法 が存在し、どの予報文もいずれかの文法にマッチします。以下は例です。

文法
天候 晴れ
天候 頻度 天候 晴れ 時々 くもり
天候 時間帯 天候 くもり 昼過ぎ から 夕方
天候 時間帯 頻度 天候 くもり 昼前 まで 時々 晴れ
天候 頻度 天候 時間帯 天候 晴れ 時々 くもり 夜のはじめ頃 から
天候 所により 天候 くもり 所により
天候 所により 時間帯 天候 くもり 所により 明け方 まで
天候 所により 修飾 所により 雷を伴い 激しく 降る
天候 所により 時間帯 修飾 所により 夕方 から 雷 を伴う
天候 頻度 天候 時間帯 天候 所により 時間帯 修飾 晴れ 時々 くもり 夕方 から 所により 夜遅く 雷を伴い 激しく 降る

正規表現

文法を正規表現で表すと、次のようになります(雪 後 雪などのありえない表現もマッチしますが、今回は無視します)。

(晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)( (時々|一時|後) (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)( (時々|一時))? (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( (所により|[^ ]+ では)( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?))? ((晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)|(雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)))?

何を言っているんだという感じなので、擬似的に表現をすると次のようになります。

天候( 頻度 天候)?( 時間帯( 頻度)? 天候)?( (所により|地名)( 時間帯)? (天候|修飾))?
項目 正規表現
天候 (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)
頻度 (時々|一時|後) ※時間帯の後ろは (時々|一時)
時間帯 ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)
地名 気象庁 | 気象警報・注意報や天気予報の発表区域 の「市町村等をまとめた地域」に準じる
修飾 (雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)

※「雪 で ふぶく」等は修飾が入っていますが、簡単のために天候にまとめています。
※カバー出来ていない天気予報文が存在する可能性があります。

テスト

下記サイトで試してみるとマッチします。

f:id:no_clock:20191021004546p:plain

天気予報文とパーサ

正規表現のままパーサを書くのはつらいため、より簡単にして概ね次のルールでパーサを書きますました。

  • 入力は予報文、出力は0〜21時の3時間ごとの予報
  • 「時間帯」「所により(地名)」「何もない(終端)」が出現した場合に分割
  • 「所により」「頻度」が含まれる場合は予報を追記。それ以外は指定時間帯の予報として上書き

動作

$ ruby runner.rb

天気予報を入力: 晴れ 時々 くもり 夕方 から 雨 所により 夜遅く 雷を伴い 激しく 降る
解析結果:
{:wind=>nil,
 :weather=>
  {0=>"晴れ時々くもり",
   3=>"晴れ時々くもり",
   6=>"晴れ時々くもり",
   9=>"晴れ時々くもり",
   12=>"晴れ時々くもり",
   15=>"雨",
   18=>"雨",
   21=>"雨所により雷を伴い激しく降る"}}

天気予報を入力: 北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨
解析結果:
{:wind=>"北東の風 日中 東の風",
 :weather=>
  {0=>"くもり時々晴れ",
   3=>"くもり時々晴れ",
   6=>"くもり時々晴れ",
   9=>"くもり時々晴れ",
   12=>"くもり時々晴れ",
   15=>"くもり",
   18=>"くもり",
   21=>"くもり所により雨"}}

3時間ごとの天気予報がわかるようになりました。めでたしめでたし。

同じ構図で何枚も撮って,最も手ブレしていない 1 枚だけを残す

結論

  • ImageMagickcompare コマンドで, Perceptual Hash (pHash) により同一構図かチェックする.
  • ImageMagickconvertidentify コマンドで, Canny 法によりエッジ検出し同一構図の n 枚から最も鮮明な 1 枚を残す.
  • ソースはここに置いた: GitHub - typewriter/unblurred-photo-picker

背景

手ブレしたくないが, ISO 感度もあまり上げたくない(綺麗に撮りたい).その場合,ギリギリのシャッタースピードで何枚も撮る,ということをよくやります.しかし,撮影後の写真の選別が極めて面倒なうえ,かなり機械的な作業です.

  1. 同一構図で撮られた写真を探し出す
  2. そのうち最も鮮明と思われる写真 1 枚を残し,それ以外を重複とする.
  3. (重複写真は Flickr 上で非公開に設定する*1

機械的な作業… ということは機械で出来るのではないでしょうか.やってみました.

同一構図の判定

同一構図の判定は,画像の類似度が高いかどうかで行うこととします.ただし,画像処理はさっぱり分からないため, ImageMagick に全力で甘えます.

ImageMagick には compare コマンドがあり,お手軽に画像比較を行うことが可能です. 画像比較に使用できるメトリクスの一覧は ImageMagick - Command-line Options#-metric type にありますが,今回は PHASH (Perceptual Hash) を用います.

# ImageMagick 7 系.小さいほど類似度が高い.
$ magick compare -metric PHASH P9240031.JPG P9240035.JPG NULL:
13.7601

Perceptual Hash ではハミング距離を取って類似度とする例を見かけましたが, ImageMagick の数値はそうではないようです.まあ気にせずいくつかの画像で試してみます.

画像1 画像2 備考 出力値
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202732j:plain 同じ構図 0.501766
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202751j:plain 少し下に向けた 8.26181
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202816j:plain 別構図 39.5355

同一構図では数値が小さく,異なる構図では数値が大きくなりました.使えそうです.

「最も鮮明な」写真の判定

やはり画像処理はさっぱり分からないため,こちらも ImageMagick に頼ります.

当初は「標準偏差 (Standard deviation) が大きければ値のバラつきが大きい→鮮明」と考えていましたが,手ブレした写真のほうが標準偏差が高いケースが複数枚でみられたため不採用です.

$ magick identify -format "{ \"Kurtosis\": %[kurtosis], \"StandardDeviation\": %[standard-deviation], \"Skewness\": %[skewness], \"Entropy\": %[entropy] }"  20171104_PB040552.JPG
{ "Kurtosis": 3.04856, "StandardDeviation": 11783.4, "Skewness": 1.91352, "Entropy": 0.760502 }
画像(切り抜き) 標準偏差
f:id:no_clock:20191014203238j:plain 11783.4
f:id:no_clock:20191014203249j:plain 11899.3

代わりに, Canny Edge Detection でエッジ検出した結果の標準偏差を用いることにします.

$ magick convert 20171104_PB040552.JPG -canny 0x1+10%+30% edge.png
$ magick identify -format "{ \"Kurtosis\": %[kurtosis], \"StandardDeviation\": %[standard-deviation], \"Skewness\": %[skewness], \"Entropy\": %[entropy] }"  edge.png
{ "Kurtosis": 13.5456, "StandardDeviation": 14823.4, "Skewness": 3.94279, "Entropy": 0.303514 }
画像(切り抜き) エッジ検出後(同左) 標準偏差
f:id:no_clock:20191014203238j:plain f:id:no_clock:20191014203811p:plain 14823.4
f:id:no_clock:20191014203249j:plain f:id:no_clock:20191014203853p:plain 11678.3

良さげな感じになりました.

スクリプト

Rubyで書きました。裏でImageMagickのコマンドガンガン実行します。

参考

*1:バックアップも兼ねているのでアップロードはしている

日用品のストック管理ツール Stokk を作った

日用品のストックを管理出来るツール「Stokk」を公開しました。

以下はポエムです。

技術選定

  • サーバをわざわざ建立するほどの機能はない
  • mBaaSと親和性が高そう
  • フロントエンドを書きたい

ということで、SPA + mBaaSで作ることにしました。

使ったもの

ツール: Adobe XD

初使用。無料の「スタータープラン」があり、アカウントさえあればシュッと使えます。

プロトタイプは作らず、ちょっと便利な図形描画ツール程度に使いました。事前にデザインや要件を整理でき、その後データやイベントの流れを練り練りするのにも大いに役立ちました。

f:id:no_clock:20190916220935p:plain
Adobe XDでのデザイン

言語: TypeScript

JavaScriptを操れる自信はなかったのでTypeScriptで書きました。エディタはVisual Studio Code

ただ、厳格に型付けをするのは諦めました。動く状態で、「thisはVueだからお前が定義したプロパティなぞ存在せんわ!」などと言われていました。「ぐ、Gradual Typingだから…」と意味不明な言い訳をしつつキャストしたりanyにしたりしました。

f:id:no_clock:20190916221003p:plain
怒られが発生している様子

フレームワーク: Vue.js + Vue Router, Bootstrap + BootstrapVue

ふわっと書きたかったのとReactは仕事で書く機会があるのとでVue.jsにしました。Vue CLIでTypeScriptも含めて一括で初期設定でき、とても楽でよいです。

デザインは安定のBootstrap。BootstrapVueも入れましたが、「BootstrapのListってBootstrapVueでどう書くんだっけ」と毎回マッピングしつつ書いていました。効率的に書けたかは分かりません。

その他ライブラリ: Lodash, Font Awesome

Lodashはdebounceに、Font Awesomeはアイコンに。

mBaaS: Cloud Firestore, Firebase Hosting, Firebase Authentication + FirebaseUI

作るサービスと親和性高そうなので全面的に依存しました。

FirebaseUIについては、 Firebase 6.6.1 と FirebaseUI 4.2.0 でドキュメント通りに書いたつもりで動作せず(firebase.auth()と書くとfirebaseがないと怒られる)。 メジャーバージョンを1つ落として FirebaseUI 3.6.1 を使用することで落ち着きました。何故なんだ。

そのほか

命名: Stokk

当初「にちまね!(日用品在庫マネージャ)」で けいおん!風にしようと思っていました。が、サブドメインを割り当てるタイミングで nichimane は無いな… と思って在庫(stock)を意図的に誤字らせました。

アニメーション

「動いているんだけど動いている感がない」という印象を受けたため、「小気味よく動いている」感を演出するべくアニメーションを設定しました。

  • アイテム追加、削除、並び替え時
  • 読み込み・保存時(スピナー表示)

f:id:no_clock:20190916221048g:plain

ソースコード

やっつけ感満載のソースコードGitHubにあります。

ページの好き嫌いを学習・分類するChrome拡張を作った

ネタです.

ベイジアンフィルタで,ページの好き嫌いを学習・分類するChrome 拡張機能を作りました.

動作サンプル

構成

学習・分類サーバがあり,Chrome拡張はそのクライアントとして機能します.

f:id:no_clock:20190706163910p:plain

全文ではなくURLを送っているのは,認証が必要なエリアのコンテンツをうっかり学習させないためです.学習データに個人情報が混ざっているとか怖くて扱えない.

学習・分類サーバ

ソースファイルと利用方法

chrome.google.com

Chromeウェブストアから拡張機能を追加するか,上記リポジトリをcloneしてデベロッパーモードで拡張機能を読み込んでください.

URLがサーバに送信されますので,気になる方は自前でサーバを立ててください.

Oracle Code Cardを触ってみる (触っただけ編)

Oracle Code Cardという謎のカードを譲ってもらった.「イベントでもらったけど使わない」「ボタン電池が高い(500円)」という情報も付属していた.

観察

赤い基板に電子ペーパーが載っている.イベントで配布するにしては贅沢だ.いくら掛かっているんだろう.

f:id:no_clock:20190613224614j:plain
表面

裏面には「Live for the Code」との記述がある.

f:id:no_clock:20190614002940j:plain
裏面

ボタン電池のホルダがあるが,LIR2450 3.6V,という珍しい電池のようだ.なんと RECHARGEABLE の記述がある.充電池.

f:id:no_clock:20190614002957j:plain
LIR2450

よく見ると,ESP-12Fチップが搭載されている.これはArduino互換のような気がする.調べてみるとどうやら確からしい.

f:id:no_clock:20190613224857j:plain
ESP-12FとWi-Fiの刻印があるチップ

ja.wikipedia.org

あっちこっちのページを探索していると,USB-シリアル(UART)変換チップも載っていて,シリアル接続で通信出来るとのこと.確かに載っている.

f:id:no_clock:20190614001520j:plain
SIL2104の刻印 (Silicon Labs CP2104)

というか,リポジトリがある.

github.com

通信

リポジトリの記述に従って接続し,スイッチON/OFFを切り替えたが何も起きない.

Aボタンを押すと,どうやらWi-Fiに接続しようとしている旨のメッセージが流れる.Type 'help' for more info. と表示されるが,直後にシャットダウンしていて反応はない.

f:id:no_clock:20190614001933p:plain
Arduino IDE シリアルモニタ

詰んだ,と思った.とりあえず適当にA/Bボタンを同時押ししてみた.

…すると,なんと当たりだったようで,電子ペーパーORACLEのロゴが現れた後,シャットダウンしなくなった.

そのままhelpと入力すると,期待通りヘルプが出てきた.うまくいったようだ.

f:id:no_clock:20190614000429p:plain
Arduino IDE シリアルモニタ

さて,どう調理しようかな.つづく.

参考

developer.oracle.com

hackaday.com

theappslab.com

死活監視をFirebase Realtime Databaseでお手軽にやってみる

死活監視といえば,たとえば MackerelUptime Robot などが使えます.

ただし,こうしたサービスは「エージェントをインストールする」か「外部からサービスにアクセスできる」必要があるほか,無料版に制限があるなど少々ネックがあります.

そこで,Firebase Realtime Databaseで死活監視チャレンジしてみました.

監視対象の条件

  • インターネットに到達できる(外部公開は不要)
  • cron または 任意の定期実行可能なジョブ管理システム がある
  • cURL または 任意のHTTPクライアント がある

監視対象: REST APIを定期的に叩く

監視対象のホストから,定期的にハートビートを送信します. データの保存  |  Firebase に記載されている Realtime Database の REST API を用います.

crontab -eでcrontabに定義を追加します.

*/10 * * * * curl -X PUT -d "\"`date '+\%Y-\%m-\%d \%H:\%M'`\"" https://examples.firebaseio.com/heartbeats/`hostname`.json

Realtime Databaseにデータが追加されます.

f:id:no_clock:20190603232659p:plain
Firebase Realtime Databaseのようす

アラート: Cloud FunctionsからSlackに通知する

アラートも作ります.Cloud Functionsで定期実行するようにします.

Node.jsですが普段あまり書かないのでクオリティはお察しです.

export const scheduledFunction = functions.pubsub.schedule('every 5 minutes').onRun((context) => {
  request.get({
    url: "https://examples.firebaseio.com/heartbeats.json",
  }, function(error: any, response: any, body: any) {
    const info = JSON.parse(body)
    for (let hostname in info) {
      const diff = Date.now() - Date.parse(`${info[hostname]}+09:00`)
      if (diff < 1000 * 60 * 30) { continue; }

      const options = {
        url: 'https://hooks.slack.com/services/<fugafuga>',
        form: `payload={ "text": "${hostname}: ${info[hostname]} 以降 heartbeatがありません." }`,
        json: true
      }
      request.post(options)
    }
  });
  return 1
});

ためしにしきい値を1分とかにして毎分実行させると,ちゃんとSlackに通知されます.

f:id:no_clock:20190603235821p:plain
ゆかりん

注意事項

おわりに

Realtime Databaseとは… という感じですが,Realtime Databaseは課金対象がストレージとトラフィックのみで,リクエスト数に制限がないため,少量のデータを高頻度にやり取りするケースによく合います.もっとも,今回の規模であればFirestoreでも無料枠で済みますが…

めでたしめでたし.

SIerから自社サービス系に転職して半年ちょっと経過した

2018年9月にSIerから自社サービス企業に転職した.会社の宣伝ではないので社名は伏せる(たどれば出るが).

退職エントリは書かなかったが,半年ちょっと経ったので考えを整理してみようとポエムにした.

学生時代

小学生でVisual Basic 6.0に触れ,中学生でソフトウェアを公開して自宅サーバも立てていた.今も続いている.

SIer時代

就職活動は熱心に行わなかった.「親を安心させたかった」というもっともらしい理由で大手グループのSIerに入った.

世の中には不気味なSIerのdis記事があふれていて,残念なことに私がかかわった現場の大半もその通りだった.

メモリは4GBで,Eclipseに統合された謎の静的コード解析ツールはファイル保存のたびに1分間フリーズした.3年前の話で,今は違うかもしれない.

なぜそんな闇が残り続けるのかというと,私は「効率化するメリットが(会社に)ないから」と勝手に解釈している.効率化してもお金にならない,そんな不思議な世界だったように思う.

何を得たか

と言っても,無だったわけではない.得たものはあったし,転職して初めて「あの経験役立ってる」と気づくこともあった.

  • 品質意識とバランス感覚.バグが損害に直結する.趣味のモノづくりとは何もかも違う世界で,意識は大きく変わった.また,人的リソースと期間が限られているなかで,どうバランスを取るか,という点は常にトライ&エラーで工夫していた.
  • OJTトレーナーやレビュア経験.コミュニケーション面で,どうすれば相手に伝わるのかひたすら苦心した.技術面(?)では,何をレビューしなくても大丈夫かという勘所(手の抜きどころ)を得たような気がする.
  • 会社はそう簡単には変わらないという感触.会社の技術推進部門に異動させてもらって,「さて,会社全体で技術重視の方向に舵を切ってもらいますか」とイキったが特に何も出来なかった(一番悲しいのはその障壁が部門内にあったことだった).

何を得なかったか

一方,得たいと思っていたものはあまり得られなかったように思う.

  • 技術力.趣味で培った以上のことを何も得なかった.配属直後から「チームで一番技術に詳しいのはお前だから」と言われていた.
  • 顧客が本当に求めるもの.本当に顧客が欲しているものを作っている,という感触は得られなかった.これ以上の言及は控えたい.

なぜ転職しようとしたか

「得なかったもの」の裏返しである.技術力を活かして,顧客が本当に求めるものを作りたかった.

より根底には,3つの事柄が関係している気がする.

  • 小学生の頃から続く「プログラミングって楽しい」とか「プログラミングなら何でも出来る」という感覚
  • 作ったものを誰かに使ってもらったときの「嬉しい」という感情
  • 「やりたいこと」「できること」「求められること」が合致するとベターだという大学の講義で聴いた話

ずっと転職を考えていたが,最後のひと押しは同僚の転職だった.

なぜ転職まで6年強在籍していたか

2点ある.

  • 限界を見たかった.自分が現場や会社をどれだけ変えられるか,一通り試しておきたかった.
  • 漠然とした不安.Web系はSIerよりハードワークなのだろうという偏見と,SIerから転職出来るだろうかという不安があった.

限界を見たという納得感と,SIerに残り続けることの不安にかられ,転職に至った.

なぜ今の会社に決めたか

先に非礼を詫びておく.

情熱プログラマに「一番の下手くそでいよう」という節があるが,これを最も実現できそうだったから,というのが理由である.

言い訳がましいが,このフレーズに甘えるつもりはなく,相応の成果を出すようには努めている.

転職活動の思い出

転職活動は大変だったが楽しかった.面接の技術的な質問は腕試し感覚だったし,何より「サービスを作ってる人が目の前にいる」のがとても眩しかった.

印象的な問答はよく覚えている.

私「今の会社だと,そもそも『理想の開発環境』にこぎつけるまで10年単位で掛かる.待っていられない」
面接官「その『理想の開発環境』というのは具体的には」
私「CIが回っているとかそのレベル」
面接官「えっ(笑)」
私「えっ(笑)」

SIerから自社サービス,で異世界転生モノが書けるのではないだろうか.私は書かないが.

今の会社はどうか

自分のスタイルとの合致度は大きく上がったと思っている.

会社固有のものか,自社サービス系なら当たり前のものか,という区別はついていない.

合っているところ

  • 自由.「成果を出す」という一点が満たせれば,いつ来ていつ帰ってもよいし,技術選定に制限はないし,周辺機器や書籍も手に入る.
  • 技術スタックの分散が大きい.新しい技術に触れやすいし,どの技術も誰かが詳しいので困ったときもなんとかなる.
  • コミュニケーションがいい意味で雑.相互のリスペクトが前提となるが,オブラートや遠慮みたいなところに脳のリソースを割かなくて良い.これはエンジニアに限った話ではない.
  • チームリーダでもコードが書けるSIerだとリーダはリーダ業に徹していたが,現職はメンバの自走力(?)が高くて,リーダリーダする必要はない(そっと背中を押す感覚でいる).メンバと同じく作業していることが多い.

合っていないところ

  • 申請方法どこに載ってんだみたいなことが多い.相手の時間を奪いたくなくて聞くのは避けたい人間なのだけど,結局初回は聞くことになりがち.個人的にちょっと相性が悪い.

そのほかの感想

  • みんなすごい(こなみかん).インターンの学生がめちゃくちゃ成果出したりもする.正直劣等感に苛まれることもあるが,隣の芝生は何とやら,自分は自分なりに成果を出せるポイントを見つけてやっていくしかないのではと感じている.

今何をやっていて,今後どうしたいか

今は,後述する「やりたいこと」に掲げたことをやっている.

今後については,「5年後にXX」といった明確な目標はまったくない.これには,大学生の頃に聴いた 計画的偶発性理論 (Planned Happenstance Theory) に従っている,という背景がある.

ただし,やりたいこととやりたくないことは持つようにしている.内容は変化するとしても,やりたいことが出来て,やりたくないことを極力やらなくていい,そんな場所を今後も漂っていくのだと思う.

やりたいこと

  • コーディング.ソフトウェアアーキテクチャの設計等も含む.何らかの形で「自分が書いたコード」がプロダクトに入っていてほしいと常に思っている.
  • プロダクトの成長.「より多くのユーザにより良いプロダクトを届ける」ことで満たされた気持ちになるような気がしている.これはここ半年で強く思うようになった.
  • プライベートでのサービス開発.既に14年半続けているが,アイデンティティとして昇華させたい.

やりたくないこと

  • マネジメント中心の仕事.人をどうこうする,というのは最も苦手な部分であるし,精神的に疲弊するため.
  • 兼務マルチタスクは疲弊する.

おことわり

これはポエム.一個人の意見に過ぎない.