プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

OverpassAPI の QL でいろいろと試してみました (1/2)

以前に少し試した OverpassAPI ですが、ちょっとやってみたいことができたので、改めて OpenStreetMap からのデータの取り出し方を復習してみました。

とその前に。

Overpass API のチュートリアルを見つけたので、まずは下記を試してみるのもおすすめです。

Overpass Tutorial

このチュートリアルも含めて、いろいろとやってみて、気になった点をまとめておきます。

クエリと結果セット

Overpass API のクエリって、結果をある集合に書き込みます。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name"];
out;

この場合は、デフォルトの結果集合に書き込んでいます。結果集合に名前を付けて、明示的に操作することもできます。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name"] -> .ise;
.ise out;

デフォルトの結果集合を明示的に操作したい場合は、アンダースコア(_)で表現します。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name"] -> ._;
._ out;

最初の例と同じことになります。

複数のクエリ

Overpass API では複数のクエリを書くこともできます。 で、クエリ毎に結果集合を切り替えることができるので、あとから、個別に処理することもできます。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name" = "伊勢市役所"] -> .ise;
node["name" ~ "伊勢支店"] -> .s;
(
  .ise;
  .s;
);
out;

丸かっこは、前の Overpass API の記事でやった、和集合をつくるやつです。 伊勢市役所が1点と金融機関の支店が2点見つかっています。

出力先が、同じ結果セットであれば、2つ目のクエリが入力集合を取らない場合は、前の結果が上書きされます。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name" ~ "伊勢支店"] -> .s;
node["natural"] -> .s;
.s out;

こんな感じで、 natural タグが定義されている結果に上書きされています。ということは、これをデフォルトの結果セットで試すと、2つ目のクエリの結果だけが得られることになります。

以前の記事で、絞り込みを試したときに、ちょっと感じた違和感がこれで明らかになりました。つまり、2つ目のクエリが、結果集合を上書きしていたため、絞り込みになっていなかったということですね。

複数の out ステートメント

上記では、複数のクエリを書くことができるとしましたが、もっというと、クエリだけではなく複数のステートメントを書くことができます。

ということで、 out ステートメントを複数を複数使うこともできます。

[bbox: 34.46326, 136.66065, 34.56397, 136.80004];
node["name" = "伊勢市役所"];
out;

node["name" ~ "伊勢支店"];
out;

画面上で『データ』を選択すると、一つの XML にまとまっているので、結果を一つにまとめるのは、 Overpass API でよしなにやってくれてるんでしょうね(apiを直接呼び出しても同じく1つのXMLが返ってきてました)。

way について

Overpass turbo でwayだけを表示するには

way({{bbox}})["highway"];
out geom;

または

way({{bbox}})["highway"];
(._;>;);
out skel;

のようにします。さきほどまでの伊勢市全体だとデータ量が多すぎるため、表示していた画面の範囲に限定しました(緯度経度の範囲だと、南、西、北、東の並びで 34.48677, 136.7046, 34.49306, 136.71331 でした)。

両者の違いは、戻ってきたデータの形式の違いです。 前者は、

<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="Overpass API 0.7.59 e21c39fe">
<note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note>
<meta osm_base="2022-12-19T07:32:05Z"/>

  <way id="23429311">
    <bounds minlat="34.4904910" minlon="136.7079073" maxlat="34.4914138" maxlon="136.7087511"/>
    <nd ref="1303266857" lat="34.4904910" lon="136.7079073"/>
(中略)
  <way id="50049599">
    <bounds minlat="34.4660951" minlon="136.7062005" maxlat="34.4880673" maxlon="136.7215013"/>
    <nd ref="253746243" lat="34.4660951" lon="136.7215013"/>
    <nd ref="1815327956" lat="34.4661111" lon="136.7212954"/>
(中略)
    <tag k="source" v="Bing 2007;GSImaps/std"/>
    <tag k="surface" v="paved"/>
  </way>
(後略)

のように、wayの要素内にnd要素があり、これに緯度経度の情報が付加されています。

一方、後者の場合は、

<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="Overpass API 0.7.59 e21c39fe">
<note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note>
<meta osm_base="2022-12-19T07:27:07Z"/>

  <node id="253746243" lat="34.4660951" lon="136.7215013"/>
  <node id="253746281" lat="34.4771808" lon="136.7095462"/>
(中略)
  <way id="50049599">
    <nd ref="253746243"/>
    <nd ref="1815327956"/>
(中略)
  </way>
(後略)

のように、 way 要素内にある nd 要素には node 要素のIDのみが記されており、対応するid属性を持つnode要素に緯度経度が記されています。

node もまとめて表示されるケース

ちなみに、

way({{bbox}})["highway"];
out;

とすると、

のような警告が表示されます。これは、通常 out (out body) だと、way を構成する node の情報として id は含まれますが、その node の緯度経度がないため、描画できないためです。

なので、『クエリの修復』を行うと

way({{bbox}})["highway"];
/*added by auto repair*/
(._;>;);
/*end of auto repair*/
out;

のようになります。もちろん、これでもよいのですが、この場合、node によっては交差点や信号なども表示されることがあります。

再帰

さきほどの例は、こうも書けます。

way({{bbox}})["highway"];
//(._;>;);
(._;node(w););
out;

node(w) は入力集合の way に対する node を返すというフィルタ)だそうです。

さきほどの > (再帰下降クエリ))との違いは、再帰下降クエリの場合は、入力集合にリレーションがある場合、そのリレーションのメンバーである way や node も出力する、ということのようです。

なので、上記の例では同じ結果になりましたが、入力集合によっては異なる結果になりそうです。

ちなみに、再帰下降クエリと >> (再帰下降リレーションクエリ))の違いは、リレーションのメンバーにリレーションが含まれた場合に、さらにそのリレーションのメンバーも含めるかどうか、ということのようです。

再帰フィルタや再帰クエリはいろいろなバリエーションがあるようなので、詳しくはマニュアルをご覧ください。

まとめ

いろいろと試していくと、だんだんとやり方が見えてきました。

ですが、上記の基本的な部分のほかに、頭を悩ませたものに、 area があります。長くなるので、そちらはまた別の記事にまとめたいと思います。

Rainloop 1.17.0 にアップデートしました(Rainloop 開発停止?)

昨年末以来, Rainloop の開発が滞っているようです。もともと、あんまり活発に開発されている印象がなかったのですが、特に問題もなかったので、利用していました。

が、ふと思い立って、webサイトを見ると、有料版がなくなってます。

あれれ?と思って調べてみると、どうも、2022年の9月ごろに、有料版を取りやめて、既存の Community Edition を Legacy Edition として、提供する形になったようです(このタイミングでライセンスも AGPL v3 から MIT に替わっています)。

Wayback Machine で調べてみると、ちょうどそのころに、有料版の記載もなくなったようです。

Wayback Machine

また、 GitHub にも 2022/9/1 のコミットを受けて、 1.17.0 が Legacy Edition としてリリースされています。

Rainloop 自体はまだ生きてるようですが、こちらの issue の印象だとこの先はあんまり期待できそうもありません。ちょうど、2021年末に見つかったセキュリティ関係への対応で、応答がないということで、結構話題になっていました。

さて、どうするかな?

いろいろと代替できるものはありますし、

https://alternativeto.net/software/rainloop/

Rainloop の後継をうたう、 SnappyMail とかもあります。

このまま使い続けるのも不安なのですが、とはいえ、次に何を使うのかを選定するのも時間がかかるので、取り急ぎ 1.17.0 へアップデートしておきます。

一応 1.17.0 は上記のセキュリティ問題へも対応済みのようですし、これで当座をしのぐことにします。

アップデート

アップデートは、過去の記事で書いた際と同じ方法でできます。ダウンロードのURLとして下記に切り替えるだけです。

ubuntu@ip-172-30-0-235:~$ wget https://github.com/RainLoop/rainloop-webmail/releases/download/v1.17.0/rainloop-legacy-1.17.0.zip

特に問題なく、アップデートできました。

まとめ

OSS は便利なのですが、こういう開発停止(もしくは停滞)というのが難しいところですね。もちろん、後継のプロジェクトもあるので、それまで利用していたものがまったく使えなくなるわけでもないんですがね。

この冬休みは、 Rainloop の後継探しになりそうな予感です。

森ソフトのサイトに事例紹介を追加

仕事にちょっと余裕ができたので、森ソフトのサイトをアップデートしてみました。ついでに、事例紹介をいうページを追加してみたので、その際 hugo でつまずいたところなどをメモっておきます。

hugo および テーマのアップデート

まずは、ずいぶんと放っておきっぱなしだったので、 hugo と テーマ (universal) を新しくしておきます。

hugo のアップデート

まずは、 hugo から。

mor@DESKTOP-DE7IL4F:~/tmp$ wget https://github.com/gohugoio/hugo/releases/download/v0.108.0/hugo_0.108.0_linux-amd64.deb
mor@DESKTOP-DE7IL4F:~/tmp$ sudo apt purge hugo
mor@DESKTOP-DE7IL4F:~/tmp$ sudo apt install ./hugo_0.108.0_linux-amd64.deb 
mor@DESKTOP-DE7IL4F:~/tmp$ hugo version
hugo v0.108.0-a0d64a46e36dd2f503bfd5ba1a5807b900df231d linux/amd64 BuildDate=2022-12-06T13:37:56Z VendorInfo=gohugoio
mor@DESKTOP-DE7IL4F:~/tmp$ 

hugo のプロジェクトのディレクトリに移動して、今のサイトを表示させてみます。

mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite$ hugo server
Start building sites … 
hugo v0.108.0-a0d64a46e36dd2f503bfd5ba1a5807b900df231d linux/amd64 BuildDate=2022-12-06T13:37:56Z VendorInfo=gohugoio
(中略)
Built in 2445 ms
Watching for changes in /mnt/d/work/own_app/mywebsite/{archetypes,content,data,i18n,layouts,static,themes}
Watching for config changes in /mnt/d/work/own_app/mywebsite/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

特に問題なく動作してますね。

テーマをアップデート

テーマも随分と更新されていますので、追いつきます。

mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ git tag
1.0.0
1.0.1
(中略)
1.2.5
1.3
1.3.1
mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ 

1.3.1 までしか表示されません。が、Github を見ると1.4までリリース済みのようです。なので、タグを更新します。

mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ git fetch --tags
From https://github.com/devcows/hugo-universal-theme
 * [new tag]         1.3.2      -> 1.3.2
 * [new tag]         1.4        -> 1.4
mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ git tag
1.0.0
1.0.1
(中略)
1.2.5
1.3
1.3.1
1.3.2
1.4
mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ 

1.4 まで表示されるようになりましたね。

テーマを更新します。

mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ git checkout tags/1.4
Previous HEAD position was 00e1f20 Bump ansi_regex (#337)
HEAD is now at 1af54bd Not fail when folders doesn't exist. (#371)
mor@DESKTOP-DE7IL4F:/mnt/d/work/own_app/mywebsite/themes/hugo-universal-theme$ 

あれ?更新されませんね。

で、ここで更新したいと思って、あれこれ試しているうちに、 submodule の更新がうまくできなくなってしまいました。なので、一度テーマのディレクトリを削除してから、再度 git submodule update してやり直したら、問題なく更新できました。

テーマの一部をカスタマイズしているので、最新のテーマのファイルを対象に、カスタマイズを反映します。やりかたはこちらの記事と同じです。

ここまでできたら、ローカルで表示して問題ないことを確認しておきます。

事例紹介の追加

元々の universal は、ブログの記事(recent_post)から最新の4つをトップページに表示する構成になっています。森ソフトのサイトでは、ブログははてなブログを利用しているため、この部分を変更して『新着情報』としてニュースなどの告知に使っています。

そのほかにも、もともとのテーマだと、 testimonials (お客様の声)とか clients (顧客)とかも表示できるようになっています。これらを変更して事例紹介を作ろうかとも思ったのですが、トップページに表示する部分と詳細記事の部分から構成される形になるので、やはり前述のブログの部分(新着情報)を改良するのが手っ取り早そうです。

トップページ内で新着情報を表示する部分は、 layout/partials/recent_posts.html にまとまっているので、これを case_stydies.html の名前でコピーして、トップページに表示可能とします。

(前略)
        {{ partial "top.html" . }}

        {{ partial "nav.html" . }}

        {{ partial "carousel.html" . }}

        <!-- ここに移動 -->
        {{ partial "recent_posts.html" . }}

        {{ partial "features.html" . }}
(中略)
        {{ partial "see_more.html" . }}

        <!-- 位置を移動
        {{ partial "recent_posts.html" . }}
        -->

        <!-- 開発事例を追加 -->
        {{ partial "case_studies.html" . }}

        {{ partial "clients.html" . }}

        {{ partial "footer.html" . }}

    </div>
    <!-- /#all -->

    {{ partial "scripts.html" . }}

  </body>
</html>

(元々のテーマと表示する順番を少し変えています。)

recent_posts.html のファイルを見ると、最新の記事を取得する部分は、

                {{ $posts := .Paginate (where site.RegularPages "Type" "in" site.Params.mainSections) }}
                {{ range first 4 $posts.Pages }}
                <div class="col-md-3 col-sm-6">

となっているので、case_studies.html ではこの部分を content 以下に事例紹介記事を保存するフォルダ名である case_study を使って、

                {{ $posts := .Paginate (where site.RegularPages "Type" "case_study") }}
                {{ range first 3 $posts.Pages }}
                <div class="col-md-4 col-sm-6">

としておきます(表示する記事数も変更してます)。繰り返しになりますが、実際の事例紹介の記事は、 content/case_study/ 以下に追加するようにします。

これで、表示できるはずです。試してみると、

あれ?事例紹介にも新着記事が表示されます。

これはいったい、どうなってるんだ?

原因:同一ページ内で paginator を複数持てない

いろいろと調べまくったのですが、なかなか原因がわかりません。ふとその時、新着情報の部分を隠しても、表示がおかしいのだろうか?と思いついて、試してみると、ちゃんと表示されます。

ん?最初に取得したデータがキャッシュされてるようだぞ。で、調べてみると、ありました。

Pagination | Hugo

こちらの記事に、

If you call .Paginator or .Paginate multiple times on the same page, you should ensure all the calls are identical. Once either .Paginator or .Paginate is called while generating a page, its result is cached, and any subsequent similar call will reuse the cached result. This means that any such calls which do not match the first one will not behave as written.

としっかりと、2回呼び出しても最初のものが使われるよ、とあります。これですね。

なんでこんな仕様なんだろうかとよくよく考えてみると、異なる記事のセットに対して、ページネーションを行うということは、当然ページ数とかも違うわけだから、前のページの記事群や次のページの記事群を取ろうと思ったとき、ページが同一じゃないと困る、ということなんでしょうね、きっと。

本来はこの部分は、対象とする一連の記事群から、最新の記事を固定数分だけ取り出すだけでよく、ページ制御(移動)自体は不要なので、わざわざこれを使う必要もないはずです。

まあ、テーマの作者の意図まではわからないので、 recent_posts.html の部分はそのままにしておいて、コピーした事例紹介の部分(case_studies.html)については、

                {{ range first 3 (where .Site.RegularPages "Type" "case_study") }}
                <div class="col-md-4 col-sm-6">

のようにしておきます。

これで試してみると、正しく表示できました。

追加した記事が表示されない

さて、これで、事例紹介の機能を追加できたので、いくつかの記事を追加していきます。

すると、 hugo server でローカルで表示する際に、なぜか表示されない記事があります。記事の front matter に draft がないにも関わらず、表示されません。

こちらもかなり悩みましたが、わかってみれば簡単で、記事の日時が未来の日付になっていると、デフォルトでは公開されない、という仕様でした。

Basic usage | Hugo

上記を読むと、

  • draft が true
  • 未来の日付
  • 期限切れの記事

はデフォルトでは公開されないとのことです。

で、これらを表示したければ、 hugo server を実行するときにオプションを指定する必要があるとのことです。例えば、

hugo server -D -F

ということですね。 こうすると、無事に記事が表示されました。

GA4 に対応

hugo および hugo-univesal-theme はすでに Google Analytics の GA4 にも対応済みのようで、 トラッキングコードを GA4 のものに変更すれば、そのまま使えます。

終わりに

たまにしか hugo を触らないので、これなんだっけな?となってしまいがちです。そんなおり、下記の記事

静的サイトジェネレータ「Hugo」と技術文書公開向けテーマ「Docsy」でOSSサイトを作る | さくらのナレッジ

が一通りまとまっていて、思い出すのに重宝したので、載せておきます。ご参考までに。