【備忘録】Google Maps APIを使用した店舗検索

下記2パターンの店舗検索を実装した際の備忘録
- シンプルな店舗検索
- マーカー クラスタを使用した店舗検索

## シンプルな店舗検索
マップに複数マーカーを立てるシンプルな店舗検索。
マーカーが密集すると重なって操作性が悪いため、
店舗数が多く全国展開しているようなサービスには向かない。

### 公式チュートリアル

https://developers.google.com/codelabs/maps-platform/google-maps-simple-store-locator?hl=ja#0

### 実装の流れ
1. 地図の作成
2. マーカーのデータ（GeoJSON）を読み込んでマーカーを立てる
3. マーカーのクリック時の処理を追加する

#### 地図の作成
チュートリアルにある下記のコードが、マップ作成のために必要な最小限のコード。
関数`initMap`をAPIのJS読み込みタグにパラメータ`callback`に渡すことで、
マーカーのない必要最低限のマップが作成される。

要件に合わせて関数`initMap`に処理を追加していく
```html:index.html
<!-- The div to hold the map -->
<div id="map"></div>

<script src="app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a"></script>
```

```js:app.js
function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
    });

}
```

#### 店舗のデータを読み込んでマーカーを立てる
チュートリアルを参考に作成したGeoJSONを、関数`initMap`内で`map.data`に渡すことでマップにマーカーが立つ。

```js:app.js
function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});
}
```

GeoJSONをファイルパスではなく変数で読み込む場合、`addGeoJson`を使用する。
```js:app.js
const stores =
{
    "type": "FeatureCollection",
    "features": [{
            "geometry": {
                "type": "Point",
                "coordinates": [-0.1428115,
                    51.5125168
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Modern twists on classic pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Mayfair",
                "phone": "+44 20 1234 5678",
                "storeid": "01"
            }
        },
};

function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
  });

  // GeoJSONを変数で渡す場合
  map.data.addGeoJson(stores);
}
```

#### マーカーのクリック時の処理を追加する
`map.data.addListener`にクリックイベントを追加する。
`infoWindow`はマーカーの周りに表示されるポップアップで、ソースコードや表示位置などを設定できる。
```js:app.js
function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});

  const infoWindow = new google.maps.InfoWindow();

  // Show the information for a store when its marker is clicked.
  map.data.addListener('click', (event) => {
    const category = event.feature.getProperty('category');
    const name = event.feature.getProperty('name');
    const description = event.feature.getProperty('description');
    const hours = event.feature.getProperty('hours');
    const phone = event.feature.getProperty('phone');
    const position = event.feature.getGeometry().get();
    const content = `
      <h2>${name}</h2><p>${description}</p>
      <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
    `;

    infoWindow.setContent(content);
    infoWindow.setPosition(position);
    infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
    infoWindow.open(map);
  });
}
```


## マーカー クラスタを使用した店舗検索
密集したマーカーをクラスタ化した店舗検索。
店舗数が多く全国展開しているサービスでは、
マーカーのクラスタ化が必要になる場合が多い。

### 公式チュートリアル

https://developers.google.com/maps/documentation/javascript/marker-clustering?hl=ja#maps_marker_clustering-javascript

チュートリアルが古いマーカー`google.maps.Marker`を使用した例のため、
下記を参考に新しいマーカー`AdvancedMarker`を使用する。

https://developers.google.com/maps/documentation/javascript/advanced-markers/add-marker?hl=ja

### 実装の流れ
1. Marker Clustererの読み込み
1. 地図の作成
2. マーカー配列を読み込んでマーカーを立てる
3. マーカーのクリック時の処理を追加する


#### Marker Clustererの追加
マーカーのクラスタ化するには`markerclusterer`というJSを追加で読み込む。
読み込み方法は色々あるが、下記はCDNを使用する例
```html:index.html
<!-- The div to hold the map -->
<div id="map"></div>

<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
<script src="app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a"></script>
```

#### 地図の作成
新しいマーカー`AdvancedMarker`を使用する場合、`mapId`が必要になるため追加する。
```js:app.js
function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
        mapId: 'MAP_ID',　// AdvancedMarkerを使用する場合必須
    });
}

window.initMap = initMap;
```

#### マーカー配列を読み込んでマーカーを立てる
チュートリアルを参考に店舗の座標をまとめた配列`locations`を定義し、
`locations`をmapしマーカー配列`markers`を作成する。

古いマーカーの記述は新しいマーカー`AdvancedMarker`のものに書き換える必要があるが、
チュートリアルの記法も一部古いため下記の記事を参考に修正する。

https://digipress.info/tech/migrate-to-google-maps-api-advanced-marker-element/

修正箇所は下記クラス名
- `google.maps.marker.AdvancedMarkerView` → `google.maps.marker.AdvancedMarkerElement`
- `google.maps.marker.PinView` → `google.maps.marker.PinElement`


```js:app.js
function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
        mapId: 'MAP_ID',
    });
    
    // Create an array of alphabetical characters used to label the markers.
    const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // Add some markers to the map.
    const markers = locations.map((position, i) => {
        const label = labels[i % labels.length];

        // 古いマーカー
        //const marker = new google.maps.Marker({
        //  position,
        //  label,
        //});
        
        // 新しいマーカー
        const pin = new google.maps.marker.PinElement({
          scale: 1.5,  // マーカーの大きさ( 等倍: 1)
          background: '#e44631', // マーカーの色
          borderColor: '#b01500', // マーカーの輪郭の色
          glyphColor: '#b01500', // グリフの色
          // glyph: '', // グリフを非表示にする場合
        });
        
        const marker = new google.maps.marker.AdvancedMarkerElement({
          map,
          position: position,
          content: pin.element
        });
    
        return marker;
      });

  // Add a marker clusterer to manage the markers.
  // CDN用の記述に修正
  const markerCluster = new markerClusterer.MarkerClusterer({ map, markers });

}

const locations = [
  { lat: -31.56391, lng: 147.154312 },
  { lat: -33.718234, lng: 150.363181 },
  { lat: -33.727111, lng: 150.371124 },
  { lat: -33.848588, lng: 151.209834 },
  { lat: -33.851702, lng: 151.216968 },
  { lat: -34.671264, lng: 150.863657 },
  { lat: -35.304724, lng: 148.662905 },
  { lat: -36.817685, lng: 175.699196 },
  { lat: -36.828611, lng: 175.790222 },
  { lat: -37.75, lng: 145.116667 },
  { lat: -37.759859, lng: 145.128708 },
  { lat: -37.765015, lng: 145.133858 },
  { lat: -37.770104, lng: 145.143299 },
  { lat: -37.7737, lng: 145.145187 },
  { lat: -37.774785, lng: 145.137978 },
  { lat: -37.819616, lng: 144.968119 },
  { lat: -38.330766, lng: 144.695692 },
  { lat: -39.927193, lng: 175.053218 },
  { lat: -41.330162, lng: 174.865694 },
  { lat: -42.734358, lng: 147.439506 },
  { lat: -42.734358, lng: 147.501315 },
  { lat: -42.735258, lng: 147.438 },
  { lat: -43.999792, lng: 170.463352 },
];

window.initMap = initMap;
```

#### マーカーのクリック時の処理を追加する
`infoWindow`を定義して`maker`に`addListener`でイベントを追加する。
```js:app.js
function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
        mapId: 'MAP_ID',
    });

    const infoWindow = new google.maps.InfoWindow({
        content: "",
        disableAutoPan: true,
    });
    
    // Create an array of alphabetical characters used to label the markers.
    const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // Add some markers to the map.
    const markers = locations.map((position, i) => {
        const label = labels[i % labels.length];

        // 古いマーカー
        //const marker = new google.maps.Marker({
        //  position,
        //  label,
        //});
        
        // 新しいマーカー
        const pin = new google.maps.marker.PinElement({
          scale: 1.5,  // マーカーの大きさ( 等倍: 1)
          background: '#e44631', // マーカーの色
          borderColor: '#b01500', // マーカーの輪郭の色
          glyphColor: '#b01500', // グリフの色
          // glyph: '', // グリフを非表示にする場合
        });
        
        const marker = new google.maps.marker.AdvancedMarkerElement({
          map,
          position: position,
          content: pin.element
        });
    
        // markers can only be keyboard focusable when they have click listeners
        // open info window when marker is clicked
        marker.addListener("click", () => {
          infoWindow.setContent(label);
          infoWindow.open(map, marker);
        });
        return marker;
      });

  // Add a marker clusterer to manage the markers.
  // CDN用の記述に修正
  const markerCluster = new markerClusterer.MarkerClusterer({ map, markers });

}

const locations = [
  { lat: -31.56391, lng: 147.154312 },
  { lat: -33.718234, lng: 150.363181 },
  { lat: -33.727111, lng: 150.371124 },
  { lat: -33.848588, lng: 151.209834 },
  { lat: -33.851702, lng: 151.216968 },
  { lat: -34.671264, lng: 150.863657 },
  { lat: -35.304724, lng: 148.662905 },
  { lat: -36.817685, lng: 175.699196 },
  { lat: -36.828611, lng: 175.790222 },
  { lat: -37.75, lng: 145.116667 },
  { lat: -37.759859, lng: 145.128708 },
  { lat: -37.765015, lng: 145.133858 },
  { lat: -37.770104, lng: 145.143299 },
  { lat: -37.7737, lng: 145.145187 },
  { lat: -37.774785, lng: 145.137978 },
  { lat: -37.819616, lng: 144.968119 },
  { lat: -38.330766, lng: 144.695692 },
  { lat: -39.927193, lng: 175.053218 },
  { lat: -41.330162, lng: 174.865694 },
  { lat: -42.734358, lng: 147.439506 },
  { lat: -42.734358, lng: 147.501315 },
  { lat: -42.735258, lng: 147.438 },
  { lat: -43.999792, lng: 170.463352 },
];

window.initMap = initMap;
```


## 参考

https://developers.google.com/codelabs/maps-platform/google-maps-simple-store-locator?hl=ja#0

https://developers.google.com/maps/documentation/javascript/marker-clustering?hl=ja#maps_marker_clustering-javascript

https://developers.google.com/maps/documentation/javascript/advanced-markers/add-marker?hl=ja

https://developers.google.com/maps/documentation/javascript/advanced-markers/basic-customization?hl=ja

https://digipress.info/tech/migrate-to-google-maps-api-advanced-marker-element/