Open26

送迎ツールプロトタイプ

isocchi1123isocchi1123

function doGet() {
return HtmlService.createHtmlOutputFromFile('送迎');
}

// サーバー側で取得するデータをクライアント側に渡す
function getLatLngData(slotSelected = "AM", daySelected = "月", dateSelected) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('基本情報');
const data = sheet.getDataRange().getValues();
let locations = [];

data.forEach((row, index) => {
if (index === 0) return; // ヘッダー行をスキップ

let name = row[0]; // 利用者名
let slot = row[1].trim(); // 時間帯 (AM/PM)
let days = row[2].split(",").map(day => day.trim()); // 曜日
let address = row[3]; // 住所
let lat = row[4]; // 緯度
let lng = row[5]; // 経度
let date = row[6]; // 日付

// 緯度経度がない場合、Google Geocoding APIで住所から取得
if (!lat || !lng) {
  const geoData = getGeocodeFromAddress(address);
  lat = geoData.lat;
  lng = geoData.lng;
  sheet.getRange(index + 1, 5).setValue(lat); // E列に緯度
  sheet.getRange(index + 1, 6).setValue(lng); // F列に経度
}

// 時間帯・曜日・日付でフィルタリング
if (slot === slotSelected && days.includes(daySelected) && (!dateSelected || date === dateSelected)) {
  locations.push({
    lat: lat,
    lng: lng,
    name: name,
    address: address
  });
}

});

return locations; // マップ用の位置情報を返す
}

function getGeocodeFromAddress(address) {
const apiKey = 'AIzaSyBk1uExHU8ebX-fqOMnnxyAWSAlaAZRxHg';
const url = https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${apiKey};
const response = UrlFetchApp.fetch(url);
const json = JSON.parse(response.getContentText());

if (json.status === 'OK') {
const location = json.results[0].geometry.location;
return { lat: location.lat, lng: location.lng };
} else {
throw new Error('Geocoding API Error: ' + json.status);
}
}

function getAllUsers() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('基本情報');
const data = sheet.getDataRange().getValues();
let users = [];

data.forEach((row, index) => {
if (index === 0) return;

let name = row[0];
let lat = row[4];
let lng = row[5];

users.push({
  name: name,
  lat: lat,
  lng: lng
});

});

return users;
}

isocchi1123isocchi1123

<!DOCTYPE html>
<html>
<head>
<title>送迎ルートマップ</title>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBk1uExHU8ebX-fqOMnnxyAWSAlaAZRxHg" async defer></script>
<script>
var map;
var directionsService;
var directionsRenderer;
var markers = [];
var excludedMarkers = [];
var streetViewPanorama;
var car1Markers = [];
var car2Markers = [];
var car3Markers = [];
var routeText = ""; // ルートのテキストを保存

  function initMap() {
    const mapOptions = {
      zoom: 10,
      center: { lat: 35.6885, lng: 139.7454 }, // イオンスマイル市川の位置
    };

    map = new google.maps.Map(document.getElementById("map"), mapOptions);
    streetViewPanorama = map.getStreetView();  // ストリートビューの初期化

    directionsService = new google.maps.DirectionsService();
    directionsRenderer = new google.maps.DirectionsRenderer();
    directionsRenderer.setMap(map);

    loadLocations();
    loadAllUsers();
  }

  function loadLocations() {
    var slot = document.getElementById("slot").value;
    var day = document.getElementById("day").value;
    var date = document.getElementById("date").value;

    google.script.run.withSuccessHandler(function(locations) {
      markers.forEach(marker => marker.setMap(null)); // 既存のマーカーを削除
      markers = [];
      excludedMarkers = [];

      locations.forEach(function(location) {
        var marker = new google.maps.Marker({
          position: { lat: parseFloat(location.lat), lng: parseFloat(location.lng) },
          map: map,
          title: location.name + " - 順番: " + location.order, // 順番情報を表示
        });
        markers.push(marker);

        google.maps.event.addListener(marker, 'click', function() {
          showCustomDialog(marker);
        });
      });
    }).getLatLngData(slot, day, date);
  }

  function loadAllUsers() {
    google.script.run.withSuccessHandler(function(users) {
      var select = document.getElementById("userSelect");
      select.innerHTML = ''; 
      users.forEach(function(user) {
        var option = document.createElement("option");
        option.value = JSON.stringify(user);
        option.text = user.name;
        select.add(option);
      });
    }).getAllUsers();
  }

  function addUser() {
    var selectedUser = JSON.parse(document.getElementById("userSelect").value);
    var marker = new google.maps.Marker({
      position: { lat: parseFloat(selectedUser.lat), lng: parseFloat(selectedUser.lng) },
      map: map,
      title: selectedUser.name,
    });

    markers.push(marker);

    // 追加後のマーカークリックイベントを追加
    google.maps.event.addListener(marker, 'click', function() {
      showCustomDialog(marker);
    });
  }

  function showCustomDialog(marker) {
    var dialog = document.createElement("div");
    dialog.className = "custom-dialog";

    var text = document.createElement("p");
    text.textContent = marker.title + " をどうしますか?";
    dialog.appendChild(text);

    var addToCar1 = document.createElement("button");
    addToCar1.textContent = "1号車に追加";
    addToCar1.onclick = function() {
      addToCarTable(1, marker.title);
      car1Markers.push(marker);
      dialog.remove();
    };
    dialog.appendChild(addToCar1);

    var addToCar2 = document.createElement("button");
    addToCar2.textContent = "2号車に追加";
    addToCar2.onclick = function() {
      addToCarTable(2, marker.title);
      car2Markers.push(marker);
      dialog.remove();
    };
    dialog.appendChild(addToCar2);

    var addToCar3 = document.createElement("button");
    addToCar3.textContent = "3号車に追加";
    addToCar3.onclick = function() {
      addToCarTable(3, marker.title);
      car3Markers.push(marker);
      dialog.remove();
    };
    dialog.appendChild(addToCar3);

    var excludeButton = document.createElement("button");
    excludeButton.textContent = "欠席として除外";
    excludeButton.onclick = function() {
      excludedMarkers.push(marker);
      marker.setMap(null);
      dialog.remove();
    };
    dialog.appendChild(excludeButton);

    var streetViewButton = document.createElement("button");
    streetViewButton.textContent = "ストリートビュー";
    streetViewButton.onclick = function() {
      showStreetView(marker.getPosition());
      dialog.remove();
    };
    dialog.appendChild(streetViewButton);

    var cancelButton = document.createElement("button");
    cancelButton.textContent = "キャンセル";
    cancelButton.onclick = function() {
      dialog.remove();
    };
    dialog.appendChild(cancelButton);

    document.body.appendChild(dialog);
  }

  function addToCarTable(carNumber, name) {
    var tableId = 'car' + carNumber + 'Table';
    var table = document.getElementById(tableId);
    
    var row = table.insertRow();
    var cell1 = row.insertCell(0);
    var cell2 = row.insertCell(1);
    var cell3 = row.insertCell(2);
    var cell4 = row.insertCell(3); // 修正・削除用のセルを追加
    var cell5 = row.insertCell(4); // 順番変更用のセルを追加

    cell1.innerHTML = name;
    cell2.innerHTML = carNumber + '号車';
    cell3.innerHTML = (name === "イオンスマイル市川店") ? "" : "時間を計算中...";

    // 削除ボタン
    var deleteButton = document.createElement("button");
    deleteButton.textContent = "削除";
    deleteButton.onclick = function() {
      table.deleteRow(row.rowIndex);
      markers = markers.filter(marker => marker.title !== name); // マーカーを削除
    };
    cell4.appendChild(deleteButton);

    // 順番変更ボタン
    var moveUpButton = document.createElement("button");
    moveUpButton.textContent = "↑";
    moveUpButton.onclick = function() {
      if (row.previousElementSibling) {
        row.parentNode.insertBefore(row, row.previousElementSibling);
      }
    };
    cell5.appendChild(moveUpButton);

    var moveDownButton = document.createElement("button");
    moveDownButton.textContent = "↓";
    moveDownButton.onclick = function() {
      if (row.nextElementSibling) {
        row.parentNode.insertBefore(row.nextElementSibling, row);
      }
    };
    cell5.appendChild(moveDownButton);
  }

  function showStreetView(position) {
    var panorama = new google.maps.StreetViewPanorama(document.getElementById("streetView"), {
      position: position,
      pov: {
        heading: 34,
        pitch: 10
      },
      zoom: 1
    });
    streetViewPanorama.setPano(panorama);
  }

  function backToMapView() {
    streetViewPanorama.setVisible(false);
  }

  // ルート案内と時間計算
  function calculateAndDisplayRoute(carNumber) {
    var carMarkers;
    if (carNumber === 1) {
      carMarkers = car1Markers;
    } else if (carNumber === 2) {
      carMarkers = car2Markers;
    } else {
      carMarkers = car3Markers;
    }

    if (carMarkers.length > 0) {
      directionsRenderer.setMap(null); // 前回のルート案内をリセット
      directionsRenderer = new google.maps.DirectionsRenderer(); // 新しいレンダラーを作成
      directionsRenderer.setMap(map);

      const waypoints = carMarkers.map(marker => ({
        location: marker.getPosition(),
        stopover: true
      }));
      const origin = carMarkers[0].getPosition();
      const destination = carMarkers[carMarkers.length - 1].getPosition();

      directionsService.route({
        origin: origin,
        destination: destination,
        waypoints: waypoints.slice(1, waypoints.length - 1),
        optimizeWaypoints: true,
        travelMode: 'DRIVING'
      }, function(response, status) {
        if (status === 'OK') {
          directionsRenderer.setDirections(response);
          const route = response.routes[0];
          var legTimes = []; // 各区間の所要時間を保存

          // 各区間の所要時間を計算してテーブルに反映
          route.legs.forEach(function(leg, index) {
            var timeText = `${Math.round(leg.duration.value / 60)} 分`;
            legTimes.push(timeText);

            var table = document.getElementById(`car${carNumber}Table`);
            if (table.rows[index + 1].cells[0].innerText !== "イオンスマイル市川店") {
              table.rows[index + 1].cells[2].innerText = timeText; // 時間を更新
            }
          });

          // ルート案内の詳細テキストを生成
          routeText = "";
          route.legs.forEach(function(leg, index) {
            routeText += `ステップ ${index + 1}: ${leg.start_address} から ${leg.end_address} まで: ${leg.duration.text}\n`;
          });
          document.getElementById("routeDetails").innerText = routeText;
        } else {
          window.alert('Directions request failed due to ' + status);
        }
      });
    }
  }

  // ルートのテキスト表示をトグル
  function toggleRouteDetails() {
    var routeDetailsDiv = document.getElementById("routeDetailsDiv");
    if (routeDetailsDiv.style.display === "none") {
      routeDetailsDiv.style.display = "block";
    } else {
      routeDetailsDiv.style.display = "none";
    }
  }

</script>
<style>
  #map, #streetView {
    width: 48%;
    height: 500px;
    float: left;
  }

  .car-tables {
    display: flex;
    justify-content: space-between;
    margin-top: 20px;
  }

  table {
    margin-right: 20px;
    border-collapse: collapse;
  }

  table, th, td {
    border: 1px solid black;
    padding: 10px;
  }

  .custom-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: white;
    border: 1px solid black;
    padding: 20px;
    z-index: 1000;
  }

  .custom-dialog button {
    margin-right: 10px;
  }

  /* フォントサイズをさらに大きく調整 */
  .input-labels {
    font-size: 1.5em;
    margin-right: 10px;
  }

  select, input[type="date"] {
    font-size: 1.5em;
    padding: 5px;
  }

  button {
    font-size: 1.3em;
    padding: 5px 10px;
  }

  /* ルートテキストのスタイル */
  #routeDetailsDiv {
    margin-top: 20px;
    display: none;
  }

  /* スマホ向けルートリンク */
  #smartphoneRouteLink {
    display: none;
    font-size: 1.2em;
    margin-top: 20px;
  }
</style>

</head>
<body onload="initMap()">
<h1>送迎ルートマップ</h1>
<div>
<label for="slot" class="input-labels">時間帯選択: </label>
<select id="slot" onchange="loadLocations()">
<option value="AM">AM</option>
<option value="PM">PM</option>
</select>
<label for="day" class="input-labels">曜日選択: </label>
<select id="day" onchange="loadLocations()">
<option value="月">月</option>
<option value="火">火</option>
<option value="水">水</option>
<option value="木">木</option>
<option value="金">金</option>
<option value="土">土</option>
<option value="日">日</option>
</select>
<label for="date" class="input-labels">日付選択: </label>
<input type="date" id="date" onchange="loadLocations()">
<label for="userSelect" class="input-labels">振替利用者を追加: </label>
<select id="userSelect"></select>
<button onclick="addUser()">追加</button>
</div>

<div class="car-tables">
  <div>
    <h2>1号車</h2>
    <table id="car1Table" border="1">
      <tr>
        <th>名前</th>
        <th>車両番号</th>
        <th>時間</th>
        <th>操作</th>
        <th>順番変更</th> <!-- 順番変更ボタンを追加 -->
      </tr>
    </table>
    <button onclick="calculateAndDisplayRoute(1)">1号車のルート案内を開始</button>
  </div>

  <div>
    <h2>2号車</h2>
    <table id="car2Table" border="1">
      <tr>
        <th>名前</th>
        <th>車両番号</th>
        <th>時間</th>
        <th>操作</th>
        <th>順番変更</th>
      </tr>
    </table>
    <button onclick="calculateAndDisplayRoute(2)">2号車のルート案内を開始</button>
  </div>

  <div>
    <h2>3号車</h2>
    <table id="car3Table" border="1">
      <tr>
        <th>名前</th>
        <th>車両番号</th>
        <th>時間</th>
        <th>操作</th>
        <th>順番変更</th>
      </tr>
    </table>
    <button onclick="calculateAndDisplayRoute(3)">3号車のルート案内を開始</button>
  </div>
</div>

<div id="map"></div>
<div id="streetView"></div>

<div>
  <button onclick="toggleRouteDetails()">ルートの詳細を表示/非表示</button>
  <div id="routeDetailsDiv">
    <pre id="routeDetails"></pre>
  </div>
</div>

</body>
</html>