オライリーの特約書店をBlazorでマップ化したウェブサイトを作成した話

# 概要

みんな大好き「オライリー」の特約書店をマップとして可視化するウェブサイトをBlazor WebAssemblyで作ったので紹介します。
元は.NET 5時代に作ったものなのですが、.NET 6にマイグレーションをした上で少しだけ手を加えたりしたので改めてネタとして紹介しようかと思います。


https://nobu17.github.io/oreilly_map/

## どういったウェブサイト？

オライリーの書籍を販売している書店を、マップや一覧で確認可能なものです。
都道府県別や、現在地点から近い店舗を表示する機能なども備えてます。

旅先でオライリーの書籍が欲しくなるような、オライリー中毒のあなたもこれで大丈夫！
(もちろん、レスポンシブデザインなのでスマホでもOK)

![test2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/439a149c-3391-1bab-1685-88de1a8fa71f.gif)

全国のオライリー特約店が日本地図にマッピングされているのは壮観です。
![cap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/73d33610-a4cb-1d97-4441-691e7586582d.png)


## 情報ソースや構造など

情報のソースとしてはオーム社さんで公開されてる、オライリーの取扱店情報を使用しています。
http://www.ohmsha.co.jp/data/bookstore/bookstore_home.htm

この情報には緯度、経度がないので別途、plus codeの情報取得し緯度、経度に変換してマップ状に可視化しています。
https://maps.google.com/pluscodes/

アプリケーションとしては2つあり、

- 情報をJSonファイルとして出力するNode.jsアプリケーション(ツール)
- JSonファイルをデータストアとして使用するBlazorアプリケーション

といった構造になっています。

Blazor WebAssemblyをGitHub Page上でホストし、デプロイはGitHub Actionsを使用しています。

Node.jsとBlazor用の2つのGitHub Actionを用意し、Node.js側はスケジュールにより定期的に実行されて、情報を自動更新する仕組みになっています。

GitHub Actionsのスケジュール実行に関しては下記が参考になります。
https://qiita.com/Broccolingual/items/bf0fa0d00920fbfda371


## Blazor側使用技術

### MudBlazor

BlazorのUIフレームワークです。

https://mudblazor.com

マテリアルデザインで作られていて、CSSやHTMLを直接駆使しなくても、簡単にモダンな見た目のWEBサイトが構築できるようになります。
UIのコンポーネントも豊富で、かなり使い勝手が高い便利なフレームワークです。
今回.NET5から6にアップデートするにあたっては、破壊的な変更が何点かありましたが、簡単に対応することはできました。
(アイコン周りの名称が変わった事やインジェクトするメソッドが追加された等)

MudBlazorに関しては、@jsakamotoさんの記事が参考になります。

https://qiita.com/jsakamoto/items/da7f978e1281fabb6093


### leaflet

Webで地図を表示するためのJavascriptライブラリです。

https://leafletjs.com

地図周りの処理はこのライブラリを使用して実装しています。
BlazorはJavascriptの相互運用機能が用意されているので、Javascriptで作られた豊富な資産が利用できるのもメリットですね。

こんな感じで、地図上に表示するデータをleafletに渡してマーカーを表示し、マーカーが選択された時にBlazor側にコールバックを行う、といったような相互作用も簡単にできてしまいます。


```csharp:blazor

@inject IJSRuntime js;

<div>
    <div id="mapcontainer" style="height: @MapHeight; width: 100%; margin:0;"></div>
</div>

@code {

    // コールバック
    [Parameter]
    public EventCallback<Store> OnStoreMarkerSelected { get; set; }

    ...

    public async Task InitAsync(List<Store> stores, Domains.Models.Position position)
    {
        // jsの動的ロード
        var mapModule = await js.InvokeAsync<IJSObjectReference>("import", "./js/leaflet-map.js");
        ...
        // コールバック用に参照を渡す
        var objRef = DotNetObjectReference.Create(this);
        await mapModule.InvokeVoidAsync("initMap", objRef);
        ...
        // 表示用のデータを渡す
        await mapModule.InvokeVoidAsync("setData", stores);
    }

    // 地図上でマーカーが選択されたら呼ばれるコールバック
    [JSInvokable]
    public void OnStoreMarkerClicked(Store store)
    {
        OnStoreMarkerSelected.InvokeAsync(store);
    }
}


```


```js:leaflet

let map;
let dotnet;
export function initMap(dotNetHelper, viewPoint) {
    // コールバック用にblazor側のインスタンスを保持
    dotnet = dotNetHelper;
    ...
}

let layer;
export function setData(storeList) {
    ...
    // Blazor側から渡された店舗情報をマーカーとして設定
    storeList.forEach((store) => {
        layer = addMakerWithClickEvent(layer, store);
    });
    map.addLayer(layer);
}

// 店舗情報をマーカーとして配置
function addMakerWithClickEvent(layer, storeData) {
    try {
        const maker = L.marker([storeData.position.latitude, storeData.position.longitude], {
            draggable: false,
        }).on('click', function (e) { onMarkerClicked(storeData); }); //マーカークリック時
        layer.addLayer(maker);
        return layer;
    } catch (err) {
        console.error("add error", err);
    }
}

// blazor側にマップ上で選択された店舗情報をコールバックする
function onMarkerClicked(store) {
    dotnet.invokeMethodAsync('OnStoreMarkerClicked', store);
}

```


もともとleafletは使ったことがあり、js側での操作に慣れていたため、試してはいませんが、leafletをBlazor用にラップしたライブラリなどもあるようなのでこちらを使うともっと手軽に実装できるかもしれませんね。

有名なjs系のライブラリだと、Blazor用にラップしたものなどが用意されていて簡単に使えたりします。

https://github.com/Mehigh17/BlazorLeaflet


### GeoCoordinate.NetCore

https://www.nuget.org/packages/GeoCoordinate.NetCore/

2点間の緯度、経度から距離を算出することができるライブラリです。
現在地と各店舗の距離を算出して、最寄りの店舗を探すといった事を実現しています。

.NET FrameworkにはSystem.Device.Location.GeoCoordinateクラスで算出できますが、.NET(.NET Core)にはこのクラスがないため、こちらを使用しています。
Blazor向けに作られたものではありませんが、こういった.NET用のライブラリがそのまま使えてしまうのもBlazorの強みです。



GISなどを使った、本格的な地理情報を用いたシステムを作る際には**NetTopologySuite**を使うほうがよさそうですが、距離の精度や細かい設定なども必要ないので、とりあえずお手軽さを優先しました。

https://github.com/NetTopologySuite/NetTopologySuite


## まとめ

簡単ですが、Blazorを使用して作成したアプリケーションを紹介しました。

C#で書いたアプリケーションがGitHub PageやFirebase等の静的ページホスティングサービスで手軽にデプロイできるのが、Blazor(WebAssembly)の良さだと思うので、思いつたネタ等はとりあえず勢いで作ってしまったりすると、面白いのではないかと思います。

## 豆知識

![monk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/e38976a8-741e-d126-b28d-7f7bb3f23644.png)

ご存知な方も多いと思いますが、オライリー本の表紙で使用されている動物は、下記のイラスト集から使用されているものが多いです。

https://www.amazon.co.jp/Animals-Copyright-Free-Illustrations-Mammals-Pictorial/dp/0486237664

この書籍の最初に書いてありますが、所持者は最大4つまで1つの作品でイラストを使用する事ができます。
(それ以上は連絡と許諾が必要な模様)

kindleで電子書籍版も購入可能で、入手容易なのも嬉しいポイントです。
オライリー好きならば1冊は持っておきたいですね！！

実は今回紹介したページも、ランダムで4種類の動物の画像が出るようになっています。