ReactでGoogleMapsAPI を使用した簡単なお店情報検索機能を作成してみる。(2)

おはようございます。**ふで ばこ** です。

ReactでGoogleMapsAPI を使用した簡単なお店情報検索機能を作成してみる。(1)
https://qiita.com/hudebakononaka/items/0a1ba575ab47da0243f9

でお店情報検索機能を作成する手順について説明しました。
GoogleMapAPIを使用した単純なお店情報を検索する機能ですが、何かアプリを作るときに使える人が多いのではないかと思っています。

今回は作った機能に関して少しコードの説明をしていこうかなと思っています。コードは以下の中に入っていますので参照ください。
https://github.com/Kota-Yamaguchi/StoreSearch.git
基本的に説明するのは```front/storesearch/src```の下のソースコードで、主にGoogle Map APIに関するところのコードです。

では早速始めていきましょうか！

#前提
前提意図してReactフックを使用して書いている部分がありますが、本質としてはgoogle-maps-reactの使用の解説がメインであるので、わからなくても読めるかと思います。
フックわからないからこの記事もわからない！という方は以下の記事をお読みください。
https://ja.reactjs.org/docs/hooks-intro.html


#コード解説

PlacesAPI.jsは**storeName(お店の名前)**と**setStoreInfo(お店情報を保存するセッター)**を引数にする関数です。
内容としては、お店の名前を検索情報として、GoogleMapAPIのPlacesAPIにリクエストを投げます。その結果、返却されるお店情報を保存するような動作をしています。

```JavaScript:PlacesAPI.js

var map;
function getPlaces(storeName, setStoreInfo) {
    var tokyo = new google.maps.LatLng(35.412, 139.413);


    map = new google.maps.Map(
        document.getElementById('map'), { center: tokyo
, zoom: 15 });
    var service = new google.maps.places.PlacesService(map);
    service.findPlaceFromQuery({
        query: storeName,
        fields: ['name', 'formatted_address', 'geometry', 'place_id','photos']
    }, function (results, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            // 配列となっていますが、1件しか返ってきません
            for (var i = 0; i < results.length; i++) {
                var place = results[i];
                console.log(place);
            } 
            setStoreInfo(results[0]);

        }
    });
}
export default getPlaces;
```

Mapオブジェクトを作成して、それを引数に、```new google.maps.places.PlacesService(map)```でPlacesServiceオブジェクトを作成しています。~~実は他のところのコードでもMapオブジェクトが作成されていて実はそっちでMapは表示しています。結構無駄なことをしているのですが、どうしてもコードを分けたくて変な書き方になっていますね。書き換えれそうなので、修正するべきですね。~~

このPlacesServiceオブジェクトが今回のコードで重要になってきています。このオブジェクトが持っているメソッドを使ってお店情報を検索していきます。


一番重要なところとしては以下の部分ですね。

```
service.findPlaceFromQuery({
        query: storeName,
        fields: ['name', 'formatted_address', 'geometry', 'place_id','photos']
    }, function (results, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            // 配列となっていますが、1件しか返ってきません
            for (var i = 0; i < results.length; i++) {
                var place = results[i];
                console.log(place);
            } 
            setStoreInfo(results[0]);

        }
    });
```

これ読みにくいコードになっているのですが(ごめんなさい)
形としては

```
service.findPlaceFromQuery(queryContents, callback);
```

のようになっているわけですね。**queryContents**で検索条件を投げて、**callback関数**に返却された値を渡して処理してもらう、ってことです。
今から少し解説しますが、この辺りの詳しい内容は以下のドキュメントに記載されていますので参考にしてみてください。
https://developers.google.com/maps/documentation/javascript/places#find_place_from_query

##queryContentsの中身

queryContentsの中身では、queryに検索したい名称を入れます。今回はお店の名前を入れています。
fieldsに取得してきたい情報のフィールドをリストの形式で格納します。

```javascript:queryContents
query: storeName,
fields: ['name', 'formatted_address', 'geometry', 'place_id','photos']
```

取得できるフィールドによっては料金が変わってきます。
料金のカテゴリとしては、**Basic, Contact, Atmosphere**の3つあります。下に行くほど高いです。
ただGoogleMapsAPIは毎月2万を超えるまでは無料で使用できるので、開発する段階では料金がかかることはないかと思います。

|カテゴリ |  Fields   |    
| --- | --- | --- | 
|Basic     |   formatted_address, geometry, icon, id, name, permanently_closed, photos, place_id, plus_code, scope, types  |  
|Contact     |  opening_hours   |     
|Atmosphere     |   price_level, rating, user_ratings_total  |     


##callback関数の中身

callback関数では二つの結果を受け取り、処理が実行されています。受け取る値は以下のです。

- status : **レスポンスの状態(検索がしっかりできた、や取得できた件数が0件である ...など)** 
- results : **取得してきた内容**

```javascript:callback関数
function (results, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            // 配列となっていますが、1件しか返ってきません
            for (var i = 0; i < results.length; i++) {
                var place = results[i];
                console.log(place);
            } 
            setStoreInfo(results[0]);
        }
    }

```

関数としては、まず```status==google.maps.places.PlacesServiceStatus.OK```でレスポンスが無事返却されてきているかを確認しています。statusの確認は```google.maps.places.PlacesServiceStatus.OK```だけでしか確認していませんが、色々あります。
例えば```status==google.maps.places.PlacesServiceStatus.ZERO_RESULTS```などで条件分岐を作っておけば、取得件数が0件のときの処理ができたりします。
statusコードは以下のリンクに記載されているので、使用用途に合わせて参照してください。
https://developers.google.com/maps/documentation/javascript/places#place_search_responses

その下のfor文で取得した内容をコンソールに表示するようにしています。特に意味はありません。
配列で返されますが、一件しか返却されないです。まあ他のメソッドで複数件数返すものがあるので、ロジックをそちらに合わせているのだと思います。
他のメソッドと合わせて配列で返してもらった方が、他のメソッドを使用したときにも同じロジックで処理できるので便利でありがたいですね。

最後にsetStoreInfoにresults[0]を格納しています。results[0]なのは一件しか返ってこないのがわかっているのでそうしています。

## Places Serviceの他のメソッド

今回使用しているメソッドは**findPlaceFromQuery**ですが、他にもライブラリーとしてたくさん使用できるものがあります。
使用用途に応じて、探してみてください。以下に色々と載っています。
https://developers.google.com/maps/documentation/javascript/reference/places-service#PlacesService.findPlaceFromQuery


##お店情報の表示

最後に取得した情報を表示させます。
最後にsetStoreInfoにresultsを格納していますので、以下のコードでは```results```の内容は```storeInfo```に格納されていることを前提に以下のコードを見てください。

```javascript:StoreDetail.js

<Typography>
お店検索: <TextFild className={classes.TextFild} onChange={onNameChange} variant="outlined" label="お店の名前を入力してください" />
</Typography>
<Button onClick={onNameClick} variant="contained" color="primary"> get INFO </Button>
{storeInfo &&
	<div>
		<Card>
		<CardHeader title={storeInfo["name"]} />
			<CardContent>
				<Typography> 住所: {storeInfo["formatted_address"]} </Typography>
			</CardContent>
			<img src={storeInfo.photos[0].getUrl({ 'maxWidth': 500, 'maxHeight': 500 })} alt="image" />
		</Card>
	</div>
}

```

以下でまず住所を表示しています。これはそこまでおかしくないですね

```javascript:
<Typography> 住所: {storeInfo["formatted_address"]} </Typography>
```
少しつまずいたのが、写真を表示させるときです。以下の内容に書いているように、
https://developers.google.com/maps/documentation/places/web-service/photos

```
"photos" : [
   {
      "html_attributions" : [],
      "height" : 853,
      "width" : 1280,
      "photo_reference" : "CnRvAAAAwMpdHeWlXl-lH0vp7lez4znKPIWSWvgvZFISdKx45AwJVP1Qp37YOrH7sqHMJ8C-vBDC546decipPHchJhHZL94RcTUfP~"
   }
...

```

のようになっていると思っていたのですが、どこをどう見ても```photo_reference```が見つからなかったので、かなりつまづいた思い出があります。本来であれば、```photo_reference```をもとに写真を撮ってくるリクエストを出すと言う形なのですが、なんと```photos[0].getUrl```でそれを一気にやってもらえるようです。便利ですね。
なのでコードとしては以下のようになります。実際に設定するのは```maxWidth```と```maxHeight```だけでいいですね。

```javascript:
<img src={storeInfo.photos[0].getUrl({ 'maxWidth': 500, 'maxHeight': 500 })} alt="image" />
```

photos[0].getUrlに関してですが、どこでそれを発見したのかリソースを探し直したのですが、少し前のこともありどこにあったか見つかりませんでした。すいません。


#最後に

コード全部を解説とまでは大変なのでしませんが、説明したかったところは説明しました。GoogleMapsAPIに関しては色々とリソースがありますので、勉強に関してはあまり困ることはありませんね。