`WebGLOverlayView`で遊んでみた

# `WebGLOverlayView`で遊んでみた
## 一体何をするのか
`WebGLOverlayView`とは、先日のGoogle I/O 2021の[ウェブ プラットフォームの最新情報](https://events.google.com/io/session/97ea9417-2bba-4383-8eb4-22aee86af034?lng=ja)のセッション内で紹介された、Google MapをWebGLな3Dビューで操作できるようになった（※今のところbetaのみ）やつ。

## まずは`WebGLOverlayView`コンポーネントを作る
`WebGLOverlayView`さんは、他の`Maps JavaScript API`の例に漏れずclass継承型な取り回しで、これを生のまま使うのも今っぽくないので、コンポーネント志向なフレームワークを使ってコンポーネント化してみる。
つかうフレームワークは`React`にした。
選定理由は「なんとなく」。個人的には`React`/`Vue`どっちでも使えるし、自分しか使わんならぶっちゃけどっちでもいい[^1]ので、~~直近で触ったの`Vue`だったし、~~`React`にしてみた。

[^1]: ちなみに、チーム開発における個人的な「React/Vueどっち使う？」問題での選定基準は、「チーム内エンジニアの実力による。」で、あまり難易度上げるとキツイかなって布陣の場合は、「一応使える」ようになるまでな初期の学習曲線が比較的緩やか（日本語doc/サンプルコードの充実度も含む）なVue、「必要なものがないなら、もうそれ自作すればええやん」ができるガチ勢猛者揃いなら、深みに潜っても（必要以上にゴチャらず）相応に実装／読解難易度上がるだけで済むReact、を勧めるようにしている。（「選ぶ/決める」ではなく「勧める」なのは、どっちでも使える自分が決めてそれを押し付けるのではなく、実際に困るであろう本人たちで選んだ上で納得して責任を持って使ってもらうため。）

### コンポーネントのインターフェースを決める
「このコンポーネントは何を入力として受け取り、何を出力するのか、そして何をしないのか」を決める。
#### 入力（`props`）
- `google.maps.Map`の`mapOptions`  
→MAP ID、どの座標（経度、緯度）をどういうふうに(ズーム、視点の方位/仰角、etc.)表示するかを指定するもので、このオプションを固定にしてしまうのはコンポーネント設計上拡張性低すぎであまりにもイケてないというかダメなので`props`で外から渡す（表示する側のコンポーネントに決めさせる）
    - `Maps JavaScript API`のMAP ID  
    →Googleから1マップごとに1意に発行される（べき）ID。`mapOptions`に含まれる形で受け取る
- 検索文字列  
→地図の表示座標・表示のさせ方は自由にできるのに検索文字列は固定とか、ちょっと意味わからないので、ふつうに外から`props`で受け取る。

#### 出力(JSX)
- マップ
    - if `Maps JavaScript API`のスクリプトのロードが完了していない
        - then 非表示
    - else if MAP IDもしくはAPIキーがない
        - then 非表示
    - else （= スクリプトのロードが完了、MAP IDおよびAPIキーあり）
        - then 表示

#### このコンポーネントではしないこと
- `Maps JavaScript API`のスクリプトのロード  
→この件の本質的性質として1コンポーネントが負うべき職責ではない（共通機能として実装すべき）
- 描画済みのマップに対して、検索クエリを受け取って検索処理を発火、結果出力としてマップにピンを刺す  
→これを共通機能化すべきかについては、事前の要件事情（"現在"共通化が必要か。今はなくとも将来必要となりうる公算・期待値はどのくらいか）によるため一概には言えなく、そしてちなみにこの件では要件全てが私自身のさじ加減なので「別にしなくてもいい」という判断も可能。でも、分けといたほうがクールなので分けるよ！（ref: [関心の分離](https://www.google.com/search?q=%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2)）
- `Maps JavaScript API`のAPIキーの受け取り  
→Googleからアカウントに対して認証キーとしてに発行されるクレデンシャル（認証情報）のため、当該コンポーネント内にハードコーディングするのが不適切なのはもちろん、この値が必要なのは`Maps JavaScript API`のスクリプトのロード時なので、前述で共通機能化することとしたローダー側に任せればよく、そもそもこの値について関知する必要がないため受け取らない

#### つまり、このコンポーネントでやること
- 親コンポーネントから受け取った`mapOptions`をつかって、`Maps JavaScript API`のスクリプトのローダーによって使用可能となっているはずである`Maps JavaScript API`からマップを表示する
- 併せて、同じく親コンポーネントから受け取った検索文字列をつかって、マップ上の検索結果の座標にピンを刺す
- ついでに、表示されたマップで[three.js](https://threejs.org/)を使って視点移動なアニメーションをさせてみる （きゅうに　あたらしいようけんが　あらわれた！！）  
→まぁこれはゆーて、Google I/O 2021のデモでもやってたやつそのまんまなんですけどね。

### 実際に作ってみる
`props`の型は、（`<div>`を返すつもりなので）`React.HTMLAttributes<HTMLDivElement>`を継承[^2]しつつ、前述の`mapOptions`や`searchQuery`を加えて、こんな感じ↓↓

```ts
/**
 * props for WebglOverlayView
 */
interface Props extends React.HTMLAttributes<HTMLDivElement> {
  mapOptions: google.maps.MapOptions
  searchQuery?: string
}
```

[^2]: `Props`型を`React.HTMLAttributes<HTMLDivElement>`から継承させてるのは、`id`とか`style`とか`className`とか`data-*`みたいな属性プロパティを、コンポーネントの標準機能としてちゃんとこの子でも使えるようにしときたいけどいちいち全部書くのダルいし、てか継承すればよくね？って考えから。（多分使い方としてもあってる気がする）

で、本体はこの`Props`を`props`とする`FunctionComponent`として、こんな感じ↓↓

```ts
const Component: React.FunctionComponent<Props> = ({
  mapOptions,
  searchQuery = '',
  ...props
}) => {

  ...

  return (
    <MapWrapper
      ref={mapDiv}
      {...props}
    />
  )
}
```

ちな、`return`してる`<MapWrapper>`くんは、`<div>`な[`styled component`](https://styled-components.com/)で、こんな感じの定義↓↓

```ts
const MapWrapper = styled.div`
  width: 800px;
  height: 600px;
  pointer-events: none;
`
```

大きさの定義と、ポインターイベント（マウスとかのやつ）を抑止してる（いらないから）ってだけのやつ。大意はない。

で、[`Map`クラス(`google.maps.Map`)](https://developers.google.com/maps/documentation/javascript/reference/map?hl=ja)のコンストラクタには、マップ表示先の要素のDOMインスタンスを渡す必要があるので[`Ref`](https://ja.reactjs.org/docs/refs-and-the-dom.html)を使ってDOMの参照を引き出して渡す。

`Ref`を取り出してるところ↓↓

```tsx
  const mapDiv = useRef<HTMLDivElement>(null)

  ...

  return (
    <MapWrapper
      ref={mapDiv}
```

`Ref`から取り出したDOMの参照を使って`google.maps.Map`を `new`しているところ↓↓

```ts
      // Create a map instance.
      const map = new google.maps.Map(mapDiv.current, mapOptions)
```

ちなみに、この`google.maps.Map`さんもタダでは使えない~~半分、文字通り意味でも~~というか、`Maps JavaScript API`のスクリプトをロードしなければならないので、

```ts
  const { loaderStatus: mapApiLoaderStatus } = useMapJsLoader()
```

という感じで[カスタムフック](https://ja.reactjs.org/docs/hooks-custom.html)化してそちらにおまかせをしている感じ。[^3]
ロードが完了していると返り値である`loaderStatus`（ここでは`mapApiLoaderStatus`にリネームしている）が`GoogleMapsApiLoaderStatus.LOADED`になるようにしている。（ちなこの`GoogleMapsApiLoaderStatus`列挙子も自作したやつなのでググっても出てきません><）

[^3]: この`Maps JavaScript API`のスクリプトのロードをカスタムフック化した件は、これだけで1本記事が書けちゃう内容な気がするので割愛。もしかしたら後日ワンチャン別途記事書くかも。

そして、この`mapApiLoaderStatus`がちゃんと`LOADED`になってるか、とさっきのマップ挿入先の`Ref`がちゃんとDOMインスタンスを参照する準備ができてるか、を確認できてから`Map`クラスを`new`してる。

```ts
    if (mapApiLoaderStatus === GoogleMapsApiLoaderStatus.LOADED && mapDiv.current != null) {
      // Create a map instance.
      const map = new google.maps.Map(mapDiv.current, mapOptions)
```

そしてそれを使って（今回の本題である）`google.maps.WebglOverlayView`インスタンスを生成、`map`に適用する。

```ts
      // Create a WebGL Overlay View instance.
      const webglOverlay = new google.maps.WebglOverlayView()

      // Add the overlay to the map.
      webglOverlay.setMap(map)
```

そしてこれらは、スクリプトのロードやDOM生成待ちをする必要がある（また[SSR](https://www.google.com/search?q=universal+web+application)的観点からは、クライアント描画案件だから）などの理由で、そのためのフック（[`副作用フック`](https://ja.reactjs.org/docs/hooks-effect.html)）の[`hooks API`](https://ja.reactjs.org/docs/hooks-intro.html)である[`useEffect()`](https://ja.reactjs.org/docs/hooks-reference.html#useeffect)経由で実行している。  
以下、この辺周り全体の`useEffect()`処理↓↓

```ts
  // prepare map & webglOverlayView
  useEffect(() => {
    if (mapApiLoaderStatus === GoogleMapsApiLoaderStatus.LOADED && mapDiv.current != null) {
      // Create a map instance.
      const map = new google.maps.Map(mapDiv.current, mapOptions)

      // Create a WebGL Overlay View instance.
      const webglOverlay = new google.maps.WebglOverlayView()

      // Add the overlay to the map.
      webglOverlay.setMap(map)

      // set to component state
      setMap(map)
      setWebglOverlayView(webglOverlay)
    }
  }, [mapOptions, mapApiLoaderStatus, mapDiv, setMap, setWebglOverlayView])
```

上記の最後の`setXxx(aaa))`な処理は、`ReactComponennt`の状態変数を宣言・定義する[`hooks API`](https://ja.reactjs.org/docs/hooks-intro.html)である[`setState()`](https://ja.reactjs.org/docs/hooks-reference.html#usestate)で作った状態変数の`setter`。
以下、宣言・定義箇所↓↓

```ts
  const [map, setMap] = useState<google.maps.Map>()
  const [webglOverlayView, setWebglOverlayView] = useState<google.maps.WebglOverlayView>()
```

あとは、別途に副作用系カスタムフックとして定義した「3D描画処理（via [three.js](https://threejs.org/)）」と「検索結果の座標にピンを打つ処理」を呼び出して、おしまい。

```ts
  // draw
  useDrawer(map, webglOverlayView, mapOptions)
  // use search feature
  usePlaceService(searchQuery, map)
```

### [Storybook](https://storybook.js.org/)で確認してみる
表示するページをいちいち作らないとコンポーネントの表示・動作確認ができないのはダルいので、[Storybook](https://storybook.js.org/)を使ってサクッと確認できる（なんなら確認しながら作れる）環境を作成しておいた。  
早速使って確認してみる。

![スクリーンショット - WebglOverlayView - storybook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/4117d261-83fa-bc3e-cdc1-1378632e3fcd.png)


うん、おk。

## `WebGLOverlayView`を実際にページに埋め込んでみる（ページを作る）
### コンポーネントのインターフェースを決める
#### 入力（`props`）
- 特になし。（いらねーもん、だって）

#### 出力(JSX)
- タイトル: なんかもう適当に"WebglOverlayView"とかでいいや。
- 入力部
    - MAP ID入力欄  
        - センシティブ情報なので`type="password"`。
        - タブで次のコントロールへ移動、エンターキーで入力確定。
        - 入力確定したら再入力不可（`disabled`をセットする; 理由後述）
    - APIキー入力欄
        -センシティブ情報なので`type="password"`。
        - タブで次のコントロールへ移動、エンターキーで入力確定。
        - 入力確定したら再入力不可（`disabled`をセットする; 理由後述）
    - 入力確定ボタン
    - ボタンのラベル（表記）は「表示」とかでいいかな。
- マップ表示部
    - 先ほど作成した、`<WebglOverlayView>`コンポーネントを使う
        - MAP IDの入力が確定されるまでは、非表示でいいと思う
    - `<WebglOverlayView>`でマップを表示するための、`Maps JavaScript API`のスクリプトをロードしてくれるパーツを設置  
    →本来このようなパーツは、`page`コンポーネント（i.e. 各ページ）に実装するのではなく、`<App>`コンポーネント（`page`の更に上位（最上位）にある「（ページ間遷移etc.も含んだ）Reactアプリケーション全体」を統括・定義するコンポーネント）とかに配置すべきもの。なぜなら`Maps JavaScript API`のスクリプトは1回だけロードすればよく（別APIキーによる別途ロードはエラーになるはず）、APIキーも通常はアプリケーション固有値として一意定義されてるはずだから。ただ今回はAPIキーをテキスト入力するようにしたかったので、そのテキスト入力の管理粒度であるこのコンポーネントでスクリーンショットも行うことにした、という経緯。~~なおこれに伴い再入力を許可できなくなったため、一度入力を確定したら再入力不可にしなければいけなくなりましたとさ。~~

#### このコンポーネントではしないこと
- `<WebglOverlayView>`がやってくれること
    - 地図の表示
      - 「`Maps JavaScript API`のスクリプトのロードしてくれる さん」のロード完了を検知して地図を表示する
- `Maps JavaScript API`のスクリプトのロード
    - このひとは、MAP IDの入力を受け付けて、ロードしてくれるひとに渡して「ロードよろ」するだけ。このひと自身がロードするわけではない。
- （マップにピンを刺すためのデータを取得するための）検索文字列の入力
    - 今回はもう固定値でいいかなって。  
    →ちな、`ラーメン`にしたよ！！  
    （Q: なぜ「ラーメン」なのですか？ A: ラーメンおいしいよねー（※答えてない））

#### つまり、このコンポーネントでやること
- MAP IDとかAPIキーの入力を受け付ける。（一度入力確定したら再入力できないようにする）
    - 入力確定云々とか入力値管理（状態変数としての保持etc.）も含む
- `Maps JavaScript API`のスクリプトのローダーに、入力されたAPIキー渡してロードを依頼する
    - 「ロードそのもの」はやらない
- `<WebglOverlayView>`を設置する
    - 入力されたMAP IDを渡してマップを表示してもらう
    - MAP IDの入力が確定するまで非表示とする

### 実際に作ってみる
まずマップ表示部（およびスクリプト読み込み部）。

```tsx
        ...

        <MapBody>
          {mapId.length > 0 && apiKey.length > 0 ? (
            <MapJsLoaderProvider apiKey={apiKey}>
              <WebglOverlayView
                mapOptions={mapOptions}
                searchQuery='ラーメン'
              />
            </MapJsLoaderProvider>
          ) : undefined}
        </MapBody>

        ...
```

構成しているのは、マップである`<WebglOverlayView>`と`Maps JavaScript API`のスクリプトをロードしてくれる`<MapJsLoaderProvider>`（[`ContextProvider`](https://ja.reactjs.org/docs/context.html#contextprovider)にしてみました）で、
それぞれMAP ID、APIキーが入力されるまではまだいらない子なので、（[三項演算子](https://www.google.com/search?q=%E4%B8%89%E9%A0%85%E6%BC%94%E7%AE%97%E5%AD%90)[^4]で）`mapId.length > 0 && apiKey.length > 0`が`true`になるまでは`undefined`[^5]で表示されないようにしてる。

[^4]: 三項演算子の使用は「（時として）コードの可読性を下げる」などの理由から一部コーディング規則で禁止されている場合があります。お近くのコーディング規約・規則をご確認の上、それに則った実装を行うようにしましょう。
[^5]: JSX書くときの「こっからここまでは（これこれの条件のとき）表示しない」ってとき、ReactにTypeScriptない頃からの個人的慣習で`undefined`使ってんだけど、ReactComponentの描画ロジックの返り値の型が`ReactElement<any, any> | null`←こうなってる（つまり少なくとも「コンポーネントとして」何も表示しないよってときは「`null`を返す」が正ってこと）ってことは、部分JSXとして「ここは何も表示させないだよ」するときもこれに合わせて`null`にしたほうがいいのだろうか？まぁ文法上（よほど几帳面に書かない限り）部分JSXとして`undefined`（を含むあらゆるfalsy値）が来ることはままあるし、それを絶対`null`統一に仕様変更とかReactエコシステム爆破レベルの大リスクになる気がするから、「気にしなければ別に」な案件な気もするけれど。

ところで、[おわかりいただけただろうか](https://www.google.com/search?q=%E3%81%8A%E3%82%8F%E3%81%8B%E3%82%8A%E3%81%84%E3%81%9F%E3%81%A0%E3%81%91%E3%81%9F%E3%81%A0%E3%82%8D%E3%81%86%E3%81%8B)...

```
＿人人人人人人人人人人人人人人人人人人人人＿
＞　'ラーメン'（※ハードコーディング）　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣
```

バリカタである。（？）

次に入力部。

```tsx
        <MapHeader onSubmit={handleFormSubmit}>
          mapId: <MapIdInput type='password' name='mapId' placeholder='input your Map ID here' value={mapIdInput} disabled={mapId.length > 0} onChange={handleMapIdInputChange} />
          apiKey: <ApiKeyInput type='password' name='apiKey' placeholder='input your API_KEY here' value={apiKeyInput} disabled={apiKey.length > 0} onChange={handleApiKeyInputChange} />
          <button disabled={mapIdInput.length === 0 || apiKeyInput.length === 0 || isFormSubmitted}>表示</button>
        </MapHeader>
```

`<MapHeader>`くんは`<form>`な[`styled component`](https://styled-components.com/)、そして同じく`<MapIdInput>`くんと`<ApiKeyInput>`は`<input>`。
タブ移動だのエンターで確定だのをサクッと実現するには`<form>`の`submit`イベントが便利なのでそうしました。
で、この辺の入力管理周りが以下↓↓

```ts
  const [mapId, setMapId] = useState<string>('')
  const [mapIdInput, setMapIdInput] = useState<string>(mapId)
  const [apiKey, setApiKey] = useState<string>('')
  const [apiKeyInput, setApiKeyInput] = useState<string>(apiKey)
  const [isFormSubmitted, setIsFormSubmitted] = useState<boolean>(false)

  ...

  const handleMapIdInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    const { currentTarget: { value: mapIdInput } } = e

    if (mapIdInput.length > 0) {
      setMapIdInput(mapIdInput)
    }
  }, [setMapIdInput])

  const handleApiKeyInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    const { currentTarget: { value: apiKeyInput } } = e

    if (apiKeyInput.length > 0) {
      setApiKeyInput(apiKeyInput)
    }
  }, [setApiKeyInput])

  const handleFormSubmit = useCallback((e: React.FormEvent) => {
    e.preventDefault()

    if (mapIdInput.length > 0 && apiKeyInput.length > 0) {
      setMapId(mapIdInput)
      setApiKey(apiKeyInput)
      setIsFormSubmitted(true)
    }
  }, [mapIdInput, apiKeyInput, setMapId, setApiKey, setIsFormSubmitted])
```

まず状態変数を上から順に

- `mapId`（/`setMapId`): MAP ID（確定値）
- `mapIdInput`（/`setMapIdInput`): MAP ID（入力値）
- `apiKey`（/`setApiKey`): APIキー（確定値）
- `apiKeyInput`（/`setApiKeyInput`): APIキー（入力値）
- `isFormSubmitted`（/`setIsFormSubmitted`): `<form>`の`submit`が発火したかどうかフラグ

で、「入力値」と「確定値」をぞれぞれわざわざ分けてるのは

- **入力値**: `<input>`の`change`イベントで毎回変更。何か入力されるたび（≒1文字づつ）値が変わる。
- **確定値**: 確定（`<form>`が`submit`）されるまでは`''`（空文字）、確定されたときに「入力値」が反映される。（マップたちの表示/非表示管理や、`Maps JavaScript API`スクリプトのロード管理に便利！！）

という感じ。

最後は、MAP IDは`Map`に渡すときに`mapOption`のプロパティとして渡さないといけないので、

```ts
/**
 * default options for `WebglOverlayView`
 */
const mapOptionsDefault: Omit<google.maps.MapOptions, 'mapId'> = {
  tilt: 0,
  heading: 0,
  zoom: 18,
  center: { lat: 33.590188, lng: 130.420685 },
  // disable interactions due to animation loop and moveCamera
  disableDefaultUI: true,
  gestureHandling: 'none',
  keyboardShortcuts: false
} as const

interface Props {}

const Component: React.FunctionComponent<Props> = (props) => {

  ...

  const mapOptions = useMemo(() => ({
    ...mapOptionsDefault,
    mapId
  }), [mapId])
```

こんな感じでコンポーネントの外側に定数値として`google.maps.MapOptions`のデフォルト値を定義し、コンポーネント内で[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals)を使ってマージしている。

なお、`mapOptionsDefault`の`center`は地図の中心に持ってくる経緯度で、この座標は地元福岡の主要駅である「博多駅」を指している。（そして、検索キーワードは「ラーメン」、つまり...）

ちなここで使っている[`useMemo()`](https://ja.reactjs.org/docs/hooks-reference.html#usememo)は、いわゆる[メモ化(memoization)](https://www.google.com/search?q=memoization)を行う[`hooks API`](https://ja.reactjs.org/docs/hooks-intro.html)で、メモ化要素を省けば

```ts
  const mapOptions = {
    ...mapOptionsDefault,
    mapId
  }
```

と等価。

### 動作確認
![スクリーンショット - WebglOverlayView.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/9a2df3a5-fb96-5c06-1384-2ac3766b56d5.png)

うん、できた(๑•̀ㅂ•́)و✧

## 【飯テロ】博多駅周辺のラーメン屋さんの画像（※今回の実装とはあまり関係がありません←）
最後に、博多駅周辺のラーメン屋さんの画像といっしょにおわかれをしたいと思います。（テレビ番組EDあいさつ風）
この記事を読んでいただき、ありがとうございました。

**【一風堂】白丸元味**
![【一風堂】白丸元味.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/38b989c9-231b-ecce-2b3e-73ab293b3611.png)

**【一風堂】赤丸新味**
![【一風堂】赤丸新味.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/9726efb3-56fe-871e-3c0c-a3cf430735ea.png)

**【一蘭】天然とんこつラーメン**
![【一蘭】天然とんこつラーメン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/a3211b9f-df5f-1d4a-3653-1c0214e2d6ec.png)

**【一幸舎】ラーメン**
![【一幸舎】ラーメン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/1e764b7d-60b0-e566-d146-3a7b0619c6ed.jpeg)

**【博多らーめんShin-Shin】博多Shin-Shinらーめん**
![【博多らーめんShin-Shin】博多Shin-Shinらーめん.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/15df0f0b-71fb-8f9a-fa95-b61939775365.jpeg)

**【博多担々麺とり田】担々麺**
![【博多担々麺とり田】担々麺.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/4edc3733-a73f-db65-2dd4-7ff487e7433b.jpeg)

**【元祖博多だるま】ラーメン**
![【元祖博多だるま】ラーメン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/f982a0f1-9465-e50b-441b-d8d049647640.jpeg)

**【長浜ナンバーワン】長浜ラーメン**
![【長浜ナンバーワン】長浜ラーメン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/0117468f-a94c-8473-f29e-f2d6b622a936.png)

**【元祖博多中洲屋台ラーメン一竜】とんこつラーメン**
![【元祖博多中洲屋台ラーメン一竜】とんこつラーメン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1596987/0923bc16-19d3-df90-2a4e-b4a89675932d.jpeg)

## ほんとの最後に
全ソースはこちら↓↓
[toshi00ysm/play-with-webgloverlayview - GitHub](https://github.com/toshi00ysm/play-with-webgloverlayview)