Files
rogaining_srv/supervisor/html/realtime_monitor.html
2024-11-12 07:19:18 +09:00

1298 lines
48 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>リアルタイムモニター</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.2/dist/leaflet.css" integrity="sha256-sA+zWATbFveLLNqWO2gtiw3HL/lh1giY/Inf1BJ0z14=" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.2/dist/leaflet.js" integrity="sha256-o9N1jGDZrf5tS+Ft4gbIK7mYMipq9lqpVJ91xHSyKhg=" crossorigin=""></script>
<style>
.japan-time {
font-family: 'Arial', sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0; /* 上下の余白を20pxに設定 */
}
#japanTime {
padding: 10px; /* 余白を薄く設定 */
background-color: #333;
color: #fff;
border-radius: 10px;
font-size: 100px;
text-align: center;
width: 80%;
margin: 20px auto 0; /* 余分な外側の余白を削除 */
}
.flex-master {
display: flex;
}
.flex-slave {
margin: 10px;
}
#map {
width: 800px;
height: 600px;
margin-top: 10px;
}
h1{
color: #000;
display: block;
padding-top: 5px !important;
color: #fff;
background: #2015ff;
-webkit-box-shadow: 5px 5px 0 #007032;
box-shadow: 5px 5px 0 #120d93;
height: 50px;
margin-top: -10px;
float: left;
}
button {
font-size: 16px; /* テキストの大きさ */
padding: 10px 20px; /* 内側の余白 */
border: none; /* ボーダーを削除 */
border-radius: 5px; /* 角を丸くする */
cursor: pointer; /* ホバー時にカーソルをポインタに変更 */
transition: background-color 0.3s, color 0.3s; /* 色の変化を滑らかに */
font-weight: bold;
}
#defaultBehaviorButton {
background-color: #4CAF50; /* アニメーション表示ボタンの背景色 */
color: white; /* テキスト色 */
font-weight: bold;
}
#overrideBehaviorButton {
background-color: #008CBA; /* ユーザーごとの経路表示ボタンの背景色 */
color: white; /* テキスト色 */
font-weight: bold;
}
button:hover {
background-color: #45a049; /* ホバー時の背景色(アニメーション表示ボタン) */
color: #fff; /* ホバー時のテキスト色 */
}
#overrideBehaviorButton:hover {
background-color: #007bb5; /* ホバー時の背景色(ユーザーごとの経路表示ボタン) */
}
#event_select_map{
border-left: solid 10px #27acd9;
padding: 0.75rem 1.5rem;
border-color: transparent transparent transparent blue;
font-weight: bold;
margin-bottom: 10px;
box-shadow: 3px 5px 3px -2px #aaaaaa,3px 3px 2px 0px #ffffff inset;
float: left;
margin: 0px 10px 0px 0px;
}
#map_select{
border-left: solid 10px #27acd9;
padding: 0.75rem 1.5rem;
border-color: transparent transparent transparent blue;
font-weight: bold;
box-shadow: 3px 5px 3px -2px #aaaaaa,3px 3px 2px 0px #ffffff inset;
float: left;
margin: 0px 10px 0px 0px;
}
#zekken_number_map{
box-shadow: 3px 5px 3px -2px #aaaaaa,3px 3px 2px 0px #ffffff inset;
float: left;
margin: 0px 10px 0px 0px;
}
#map_button{
float: left;
}
/* .wap{
display: block;
float: left;
display: flex;
margin-bottom: 30px;
} */
.wap {
display: flex;
flex-direction: row; /* 横一列に並べる */
flex-wrap: nowrap; /* 要素を折り返さない */
margin-bottom: 30px;
justify-content: space-around; /* 要素間に均等なスペースを設ける */
}
.wap > * {
margin-right: 10px; /* 子要素の右に余白を設ける */
}
.wap > *:last-child {
margin-right: 0; /* 最後の子要素の右余白を削除 */
}
.wap2 {
display: flex;
flex-direction: row; /* 横一列に並べる */
flex-wrap: nowrap; /* 要素を折り返さない */
}
#map_button, #map_button2{
color: #fff;
border: 2px solid #fff;
border-radius: 0;
background-image: -webkit-gradient(linear, left top, right top, from(#fa709a), to(#fee140));
background-image: -webkit-linear-gradient(left, #fa709a 0%, #fee140 100%);
background-image: linear-gradient(to right, #fa709a 0%, #fee140 100%);
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
border-radius: 100vh;
font-family: "Arial", "メイリオ";
letter-spacing: 0.1em;
}
#map_button:hover {
-webkit-transform: translate(0, -2px);
transform: translate(0, -2px);
color: #fff;
-webkit-box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
}
#map_button2:hover {
-webkit-transform: translate(0, -2px);
transform: translate(0, -2px);
color: #fff;
-webkit-box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
}
.button4{
margin: -15px 0px 0px 20px ;
width: 150px;
background-image: -webkit-gradient(linear, left top, right top, from(#fa709a), to(#fee140));
background-image: -webkit-linear-gradient(left, #fa709a 0%, #fee140 100%);
background-image: linear-gradient(to right, #fa709a 0%, #fee140 100%);
padding: 10px 15px 10px 20px;
border-radius: 100vh;
color: #fff;
margin-top: -30px;
border: 2px solid #FFF;
box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
font-weight: bold;
}
.button4:hover {
-webkit-transform: translate(0, -2px);
transform: translate(0, -2px);
color: #fff;
-webkit-box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
}
@media screen and (max-width: 767px) {
h1{
display: block;
font-size: 24px;
display: flex;
/*justify-content: center;*/
align-items: center;
padding-top: 30px;
}
.flex-slave{
display: flex;
flex-direction: column;
}
.wap{
display: block;
/* margin-bottom: 10px;*/
float: left;
}
.wap2{
display: block;
margin-bottom: 10px;
}
#event_select_map{
display: block;
width: 100%;
margin-bottom: 10px;
}
#map_select{
display: block;
width: 100%;
margin-bottom: 10px;
}
#zekken_number_map{
width: 100%;
margin-bottom: 10px;
}
#map_button{
display: block;
width: 100%;
font-size: 24px;
}
#map_button2{
display: block;
width: 100%;
font-size: 24px;
}
#event_select_map {
position: relative;
width: 100%;
}
.arrow{
position: relative;
}
.arrow::after {
color: #828282;
position: absolute;
top:18px; /* 矢印の位置 */
right: 25px; /* 矢印の位置 */
width: 13px; /* 矢印の大きさ */
height: 13px; /* 矢印の大きさ */
border-top: 3px solid #58504A; /* 矢印の線 */
border-right: 3px solid #58504A; /* 矢印の線 */
-webkit-transform: rotate(135deg); /* 矢印の傾き */
transform: rotate(135deg); /* 矢印の傾き */
pointer-events: none; /* 矢印部分もクリック可能にする */
content: "";
border-color: #828282;
}
.arrow1{
position: relative;
}
.arrow1::after {
display: block;
color: #828282;
position: absolute;
top:83px; /* 矢印の位置 */
right: 25px; /* 矢印の位置 */
width: 13px; /* 矢印の大きさ */
height: 13px; /* 矢印の大きさ */
border-top: 3px solid #58504A; /* 矢印の線 */
border-right: 3px solid #58504A; /* 矢印の線 */
-webkit-transform: rotate(135deg); /* 矢印の傾き */
transform: rotate(135deg); /* 矢印の傾き */
pointer-events: none; /* 矢印部分もクリック可能にする */
content: "";
border-color: #828282;
}
select{
appearance: none; /* デフォルトの矢印を消す */
margin-top: 30px;
width: 300px;
}
.wrapper {
display: block;
width: 100%;
max-width:330px;
margin: 0 auto;
}
#map{
width: 100%;
max-width:340px;
}
.button4{
width: 100%;
font-size: 24px;
margin: -5px 0px 20px 0px;
text-align: center;
font-weight: normal;
}
h1{
width: 100%;
padding: 10px 10px 0px 50px;
margin-bottom: -20px;
}
#showAllRoutesButton{
background-color: #4CAF50; /* 緑色の背景 */
color: white; /* テキスト色 */
padding: 10px 20px; /* 上下のパディングと左右のパディング */
border: none;
border-radius: 5px; /* ボーダーの角を丸く */
cursor: pointer; /* ホバー時にカーソルをポインタに */
transition-duration: 0.4s; /* ホバー時の遷移時間 */
}
#showAllRoutesButton:hover{
background-color: #45a049; /* ホバー時の背景色を少し暗く */
}
#clearRoutesButton {
background-color: #f44336; /* 赤色の背景 */
color: white; /* テキスト色 */
padding: 10px 20px; /* 上下のパディングと左右のパディング */
border: none;
border-radius: 5px; /* ボーダーの角を丸く */
cursor: pointer; /* ホバー時にカーソルをポインタに */
transition-duration: 0.4s; /* ホバー時の遷移時間 */
}
#clearRoutesButton:hover {
background-color: #da3a2b; /* ホバー時の背景色を少し暗く */
}
#time-slider-container {
width: 80%;
margin: 20px auto;
}
#time-slider {
width: 100%;
}
</style>
</head>
<body>
<div class="japan-time"><p id="japanTime">読み込み中...</p></div>
<div class="flex-slave">
<br>
<div class="wrap3">
<!-- <h1>リアルタイムモニター</h1> -->
<button id="defaultBehaviorButton">アニメーション表示</button>
<button id="overrideBehaviorButton">ユーザーごとの経路表示</button>
</div>
<div class="wap" style="display: none;">
<div><span id="time-display">0</span> 分前</div>
<div id="time-slider-container">
<input type="range" id="time-slider" min="0" max="100" value="100" step="1">
</div>
<form>
<div class="arrow">
<select id="event_select_map">
<option disabled value="">イベント一覧</option>
<option selected value="FC岐阜">with FC岐阜</option>
<!--
<option value="関ケ原2410">関ケ原-2024年10月</option>
<option value="養老2410">養老-2024年10月</option>
<option value="大垣2410">大垣-2024年10月</option>
<option value="各務原2410">各務原-2024年10月</option>
<option value="多治見2410">多治見-2024年10月</option>
<option value="美濃加茂2410">美濃加茂-2024年10月</option>
<option value="下呂2410">下呂-2024年10月</option>
<option value="郡上2410">郡上-2024年10月</option>
<option value="高山2410">高山-2024年10月</option>
<option value="関ケ原2409">関ケ原-2024年9月</option>
<option value="養老2409">養老-2024年9月</option>
<option value="大垣2409">大垣-2024年9月</option>
<option value="各務原2409">各務原-2024年9月</option>
<option value="多治見2409">多治見-2024年9月</option>
<option value="美濃加茂2409">美濃加茂-2024年9月</option>
<option value="下呂2409">下呂-2024年9月</option>
<option value="郡上2409">郡上-2024年9月</option>
<option value="高山2409">高山-2024年9月</option>
-->
<option value="美濃加茂">岐阜ロゲin美濃加茂</option>
<option value="養老ロゲ">養老町</option>
<option value="岐阜市">岐阜市</option>
<option value="大垣2">岐阜ロゲin大垣@イオン</option>
<option value="大垣">岐阜ロゲin大垣</option>
<option value="多治見">岐阜ロゲin多治見</option>
<option value="各務原">岐阜ロゲin各務原</option>
<option value="下呂">岐阜ロゲin下呂温泉</option>
<option value="郡上">岐阜ロゲin郡上</option>
<option value="高山">岐阜ロゲin高山</option>
</select>
</div>
</form>
<form>
<div class="arrow1">
<select id="map_select">
<option selected value="全て">全て</option>
<option value="3時間一般">3時間一般</option>
<option value="3時間ファミリー">3時間ファミリー</option>
<option value="3時間自転車">3時間自転車</option>
<option value="3時間ソロ男子">3時間ソロ男子</option>
<option value="3時間ソロ女子">3時間ソロ女子</option>
<option value="3時間パラロゲ">3時間パラロゲ</option>
<option value="3時間無料">3時間無料</option>
<option value="5時間一般">5時間一般</option>
<option value="5時間ファミリー">5時間ファミリー</option>
<option value="5時間自転車">5時間自転車</option>
<option value="5時間ソロ男子">5時間ソロ男子</option>
<option value="5時間ソロ女子">5時間ソロ女子</option>
</select>
<button type="button" id="map_button">スタート</button>
</div>
</form>
</div>
<div class="wap2" style="display: none;">
<form>
<div class="arrow3">
<select id="event_select_for_user_narrow">
<option disabled value="">イベント一覧</option>
<option value="FC岐阜">with FC岐阜</option>
<!--
<option value="関ケ原2410">関ケ原-2024年10月</option>
<option value="養老2410">養老-2024年10月</option>
<option value="大垣2410">大垣-2024年10月</option>
<option value="各務原2410">各務原-2024年10月</option>
<option value="多治見2410">多治見-2024年10月</option>
<option value="美濃加茂2410">美濃加茂-2024年10月</option>
<option value="下呂2410">下呂-2024年10月</option>
<option value="郡上2410">郡上-2024年10月</option>
<option value="高山2410">高山-2024年10月</option>
<option value="関ケ原2409">関ケ原-2024年9月</option>
<option value="養老2409">養老-2024年9月</option>
<option value="大垣2409">大垣-2024年9月</option>
<option value="各務原2409">各務原-2024年9月</option>
<option value="多治見2409">多治見-2024年9月</option>
<option value="美濃加茂2409">美濃加茂-2024年9月</option>
<option value="下呂2409">下呂-2024年9月</option>
<option value="郡上2409">郡上-2024年9月</option>
<option value="高山2409">高山-2024年9月</option>
-->
<option value="美濃加茂">岐阜ロゲin美濃加茂</option>
<option value="養老ロゲ">養老町</option>
<option value="岐阜市">岐阜市</option>
<option value="大垣2">岐阜ロゲin大垣@イオン</option>
<option value="大垣">岐阜ロゲin大垣</option>
<option value="多治見">岐阜ロゲin多治見</option>
<option value="各務原">岐阜ロゲin各務原</option>
<option value="下呂">岐阜ロゲin下呂温泉</option>
<option value="郡上">岐阜ロゲin郡上</option>
<option value="高山">岐阜ロゲin高山</option>
</select>
</div>
</form>
<form>
<input placeholder="ゼッケン番号で絞り込み" id = 'zekken_number_map'>
<button type="button" id="map_button2">click</button>
</form>
</div>
<input type='hidden' id='map_narrow_flag' value='class'>
<div class="wrapper">
<div id="map"></div>
</div></div>
<p>このアプリは岐阜県DX補助金事業で開発されました。</p>
<script>
// グローバル変数の定義
const URL_base = 'https://rogaining.sumasen.net/gifuroge';
const URL = URL_base + '/realtimeMonitor';
const URL2 = URL_base + '/getRoute';
const URL3 = URL_base + '/realtimeMonitor_zekken_narrow';
const URL4 = URL_base + '/getStartPoint';
const interval = 1; // 分で指定
const class_name = document.getElementById('map_select');
const zekken_number = document.getElementById('zekken_number_map');
const map_narrow_flag = document.getElementById('map_narrow_flag');
const event_select_map = document.getElementById('event_select_map');
const event_select_for_user_narrow = document.getElementById('event_select_for_user_narrow');
const R = Math.PI / 180;
var event_memory = "";
var polyline;
var map;
var tiles;
var marker = {};
var popUp_memory = {};
var flag_memory = {};
var url = "";
var allPolylines = [];
var allRoutesData = allRoutesData || {}; // これにより allRoutesData が未定義の場合に空のオブジェクトで初期化されます
var latestCreateTime = null; // 最新の create_at 時刻を記録するグローバル変数
var slider = document.getElementById("time-slider");
var sliderTimeDisplay = document.getElementById("slider-time-display");
var maxTimeDiffMinutes = 60; // 最大表示時間差(分)
var allMarkers = []; // 全てのマーカーを格納するための配列
// 初期化と設定
function initializeMap() {
map = L.map('map', {
center: [35.39111, 136.72222],
zoom: 10,
doubleClickZoom: false
});
tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
}
function setupEventListeners() {
document.getElementById('time-slider').addEventListener('input', function() {
var minutesAgo = this.value;
updateRoutesDisplay(minutesAgo);
});
document.getElementById('map_button').onclick = narrow_class;
document.getElementById('map_button2').onclick = narrow_zekken;
// シークバーのイベントリスナー設定をここに統合
document.getElementById('time-slider').addEventListener('input', function() {
var minutesAgo = this.value;
updateRoutesDisplay(minutesAgo);
});
}
function loadAllRoutes() {
var eventCode = document.getElementById('event_select_map').value;
var classSelect = document.getElementById('map_select').value; // map_select から class_name の選択を取得
var urlAllRoutes = URL_base + "/getAllRoutes?event_code=" + eventCode;
// classSelect が "全て" 以外の場合、URL に class_name パラメータを追加
if (classSelect !== "全て") {
urlAllRoutes += "&class_name=" + encodeURIComponent(classSelect);
}
console.log(urlAllRoutes)
var request = new XMLHttpRequest();
request.open('GET', urlAllRoutes, true);
request.onload = function () {
var data = JSON.parse(this.response);
console.log(data);
if (data.status == "ERROR") {
alert('ルート情報の取得に失敗しました');
} else {
var updatedDetail = {};
Object.keys(data.detail).forEach(function(zekken) {
var routeData = data.detail[zekken];
if (!routeData || routeData.length === 0) {
return;
}
var newKey = routeData[0].team_name || zekken; // デフォルトのキーをzekkenに設定
updatedDetail[newKey] = routeData;
var lastCreateTime = routeData[routeData.length - 1].create_at;
if (!latestCreateTime || parseDateTime(lastCreateTime) > parseDateTime(latestCreateTime)) {
latestCreateTime = lastCreateTime;
}
});
allRoutesData = updatedDetail; // 更新されたdetailオブジェクトでallRoutesDataを更新
document.getElementById('time-slider').max = 300;
document.getElementById('time-slider').value = 0;
updateRoutesDisplay(0); // ルートの表示を更新
}
};
request.send();
}
/* // メインの関数
function loadAllRoutes() {
var urlAllRoutes = URL_base + "/getAllRoutes?event_code=" + document.getElementById('event_select_map').value;
var request = new XMLHttpRequest();
request.open('GET', urlAllRoutes, true);
request.onload = function () {
var data = JSON.parse(this.response);
console.log(data)
if (data.status == "ERROR") {
alert('ルート情報の取得に失敗しました');
} else {
Object.keys(data.detail).forEach(function(zekken) {
var routeData = data.detail[zekken];
if (!routeData || routeData.length === 0) {
return;
}
routeData.sort(function(a, b) {
return parseDateTime(a.create_at) - parseDateTime(b.create_at);
});
allRoutesData[zekken] = routeData;
// 最新の create_at 時刻を更新
var lastCreateTime = routeData[routeData.length - 1].create_at;
if (!latestCreateTime || parseDateTime(lastCreateTime) > parseDateTime(latestCreateTime)) {
latestCreateTime = lastCreateTime;
}
});
// var maxMinutesAgo = calculateMaxMinutesAgo();
var maxMinutesAgo = 300;
document.getElementById('time-slider').max = maxMinutesAgo;
document.getElementById('time-slider').value = 0;
console.log(maxMinutesAgo)
updateRoutesDisplay(0);
}
};
request.send();
} */
function reload() {
var request = new XMLHttpRequest();
var showMarkers = false; // リロード時にマーカーを表示しない
if (map_narrow_flag.value == "class") {
if (class_name.value == "全て") {
url = URL + '?event_code=' + event_select_map.value;
} else {
url = URL + '?event_code=' + event_select_map.value + "&class=" + class_name.value;
for (let key in marker) {
marker[key].remove();
delete marker[key];
}
};
} else {
url = URL3 + '?event_code=' + event_select_map.value + "&zekken=" + zekken_number.value;
console.log(url)
for (let key in marker) {
marker[key].remove();
delete marker[key];
}
};
request.open('GET', url, true);
request.onload = function () {
var data = JSON.parse(this.response);
showMarkers = true
if (showMarkers) { // この条件により、showMarkersがtrueの場合のみマーカーを表示
data.forEach(function(element) {
popUp_memory[element.team_name] = "チーム名:" + element.team_name + "<br />ゼッケン番号:" + element.zekken_number + "<br />通過チェックポイント数:" + element.cp_count + "<br />合計得点:" + element.point + "<br />内減点:" + element.late_point;
flag_memory[element.team_name] = element.agent_flag;
if (element.team_name in marker) {
console.log("ok")
marker[element.team_name].setLatLng([element.latitude, element.longitude]);
marker[element.team_name].bindPopup("<p>" + popUp_memory[element.team_name] + "</p>");
marker[element.team_name].on('dblclick', onMarkerdbClick);
} else {
console.log("not ok")
marker[element.team_name] = L.marker([element.latitude, element.longitude]).addTo(map);
marker[element.team_name].bindPopup("<p>" + popUp_memory[element.team_name] + "</p>");
marker[element.team_name].on('dblclick', onMarkerdbClick);
}
});
}
// marker[element.team_name].on('dblclick', onMarkerdbClick);
};
request.onerror = function() {
console.log("Request failed");
};
request.send();
// リロード時に全ユーザーのルートを読み込む
loadAllRoutes();
}
function reload_for_user_narrow() {
var request = new XMLHttpRequest();
var showMarkers = false; // リロード時にマーカーを表示しない
map_narrow_flag.value = 'user_narrow'
if (map_narrow_flag.value == "class") {
if (class_name.value == "全て") {
url = URL + '?event_code=' + event_select_for_user_narrow.value;
} else {
url = URL + '?event_code=' + event_select_for_user_narrow.value + "&class=" + class_name.value;
for (let key in marker) {
marker[key].remove();
delete marker[key];
}
};
} else {
url = URL3 + '?event_code=' + event_select_for_user_narrow.value + "&zekken=" + zekken_number.value;
console.log(url)
for (let key in marker) {
marker[key].remove();
delete marker[key];
}
};
request.open('GET', url, true);
request.onload = function () {
var data = JSON.parse(this.response);
// 赤色のマーカーアイコンを定義
var redIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41], // アイコンのサイズ
iconAnchor: [12, 41], // アイコンのアンカーポイント
popupAnchor: [1, -34], // ポップアップのアンカーポイント
shadowSize: [41, 41] // 影のサイズ
});
showMarkers = true
if (showMarkers) { // この条件により、showMarkersがtrueの場合のみマーカーを表示
data.forEach(function(element) {
popUp_memory[element.team_name] = "チーム名:" + element.team_name + "<br />ゼッケン番号:" + element.zekken_number + "<br />通過チェックポイント数:" + element.cp_count + "<br />合計得点:" + element.point + "<br />内減点:" + element.late_point;
flag_memory[element.team_name] = element.agent_flag;
if (element.team_name in marker) {
console.log("ok")
marker[element.team_name].setLatLng([element.latitude, element.longitude]);
marker[element.team_name].setIcon(redIcon); // アイコンを赤色に設定
marker[element.team_name].bindPopup("<p>" + popUp_memory[element.team_name] + "</p>");
marker[element.team_name].on('dblclick', onMarkerdbClick);
marker[element.team_name].setZIndexOffset(1000); // マーカーを最前面に表示
} else {
console.log("not ok")
marker[element.team_name] = L.marker([element.latitude, element.longitude]).addTo(map);
marker[element.team_name].bindPopup("<p>" + popUp_memory[element.team_name] + "</p>");
marker[element.team_name].setIcon(redIcon); // アイコンを赤色に設定
marker[element.team_name].on('dblclick', onMarkerdbClick);
marker[element.team_name].setZIndexOffset(1000); // マーカーを最前面に表示
}
});
}
// marker[element.team_name].on('dblclick', onMarkerdbClick);
};
request.onerror = function() {
console.log("Request failed");
};
request.send();
// リロード時に全ユーザーのルートを読み込む
loadAllRoutes();
}
function narrow_class() {
if (polyline) {
polyline.remove()
}
map_narrow_flag.value = 'class'
checkEventMemory()
reload_for_user_narrow()
}
function narrow_zekken() {
if (polyline) {
polyline.remove()
}
map_narrow_flag.value = 'zekken'
checkEventMemory()
reload_for_user_narrow()
}
function checkEventMemory() {
const event_select_for_user_narrow = document.getElementById('event_select_for_user_narrow');
console.log(event_select_for_user_narrow.value)
if (event_select_for_user_narrow.value != event_memory) {
var request = new XMLHttpRequest();
url = URL4 + '?event=' + event_select_for_user_narrow.value
console.log(url)
request.open('GET', url, true);
request.onload = function () {
var data = JSON.parse(this.response);
var record = data.record
map.flyTo([record.latitude, record.longitude], 12);
event_memory = event_select_for_user_narrow.value;
}
request.send();
}
}
function onMarkerdbClick(e) {
console.log("ダブルクリックイベント発生");
if (polyline) {
polyline.remove();
}
console.log("ポップアップの内容:", e.target.getPopup()._content);
team_name = e.target.getPopup()._content.match(/チーム名:(.*?)<br \/>/)
//イベント中のアニメーション時は養老が選択されているのでうまくいく。ただ岐阜市のアニメを流しながらダブルクリックすると失敗する。
//event_select_for_user_narrowの値が養老に設定されていて変更できないため。イベント後修正。
//event_select_for_user_narrowの初期値を空などにして、判定し、event_selectの値を持ってくるようにするなど。
//それかゼッケン番号で、DBから持ってくるとか
event_name = document.getElementById('event_select_for_user_narrow').value
console.log(event_name);
var request2 = new XMLHttpRequest();
url2 = URL2 + "?team=" + team_name[1].replace('&','%26') + '&event_code=' + event_name
console.log(url2)
request2.open('GET', url2, true);
request2.onload = function () {
var data = JSON.parse(this.response);
if (data.status == "ERROR") {
alert('ゼッケン番号の取得に失敗しました')
} else {
var latlngs = []
var time = []
data.detail.forEach(function(element) {
latlngs.push([element.latitude, element.longitude])
time.push(element.create_at)
});
}
polyline = L.polyline(latlngs, {color: 'blue'}).addTo(map);
// distance
all_distance = 0
for (let i = 1; i < latlngs.length; i++) {
all_distance += distance(latlngs[i-1][0], latlngs[i-1][1], latlngs[i][0], latlngs[i][1]);
}
// time
start_time = new Date(time[0].replace(/\-/, "/").replace(/\-/, "/"));
end_time_set = time.pop().replace(/\-/, "/").replace(/\-/, "/")
end_time = new Date(end_time_set);
getTime = (end_time - start_time)/60/1000
// speed
speed = all_distance / getTime
if (flag_memory[team_name[1]] == "no agent") {
var popUp = popUp_memory[team_name[1]] + "<br />距離:" + orgRound(all_distance,100) + "km<br />時間:" + orgRound(getTime,100) + "分<br />平均速度:" + orgRound(speed,100) + "km/min"
} else {
var popUp = popUp_memory[team_name[1]] + "<br />距離:" + orgRound(all_distance,100) + "km<br />時間:(代理入力のため不明)<br />平均速度:(代理入力のため算出出来ず)"
};
marker[team_name[1]].bindPopup("<p>" + popUp + "</p>").openPopup();
}
request2.send();
}
function clearPolylines() {
allPolylines.forEach(function(polyline) {
map.removeLayer(polyline); // マップからポリラインを削除
});
allPolylines = []; // 配列を空にする
}
function startSliderCountdown() {
var slider = document.getElementById("time-slider");
var countdownInterval; // カウントダウン用の変数をグローバルに保持
function countdown() {
// 既存のカウントダウンがあればクリア
if (countdownInterval) clearInterval(countdownInterval);
slider.value = 45; // カウントダウン開始値を設定
countdownInterval = setInterval(function() {
slider.value--;
updateRoutesDisplay(slider.value);
if (slider.value <= 0) {
clearInterval(countdownInterval); // カウントダウンを停止
loadAllRoutes(); // データをリロード
setTimeout(countdown, 15000); // 15秒後にカウントダウン再開
}
}, 1000); // 1秒ごとに更新
}
setTimeout(countdown, 15000); // 初回のカウントダウンを15秒後に開始
}
// ユーティリティ関数
// 指定された時間差に基づいてルートをマップ上に描画
/* function updateRoutesDisplay(minutesAgo) {
console.log(minutesAgo)
// 年月日の情報(イベントの日付や現在の日付など)
var year = 2024;
var month = 0; // JavaScriptの月は0から始まるため、2月は1となります
var day = 20;
var endDate = new Date(2024, 0, 20, 16, 0, 0); // 1月20日の16:00
// 最新のcreate_at時刻を格納する変数を初期化します
var latestCreateTime = new Date(0); // 非常に古い日付で初期化
var cutoffTime = new Date(endDate.getTime() - minutesAgo * 60000);
// さらに20分遡った時刻を計算
var startTime = new Date(cutoffTime.getTime() - 30 * 60000);
// console.log(cutoffTime)
document.getElementById('time-display').textContent = minutesAgo;
Object.keys(allRoutesData).forEach(function(zekken) {
var routeData = allRoutesData[zekken];
if (!allPolylines[zekken]) {
allPolylines[zekken] = [];
}
// 既存のポリラインをマップから削除
allPolylines[zekken].forEach(function(polyline) {
map.removeLayer(polyline);
});
allPolylines[zekken] = [];
var filteredData = routeData.filter(function(point) {
var pointTime = point.create_at.includes('-') ?
new Date(point.create_at) :
new Date(year, month, day, ...point.create_at.split(":").map(Number));
// startTime 以降、cutoffTime 以前のデータをフィルタリング
return pointTime >= startTime && pointTime <= cutoffTime;
});
// 最新のスポットを見つけます
if (filteredData.length > 0) {
var latestSpot = filteredData[filteredData.length - 1]; // 最新のスポット
updateMarker(zekken, latestSpot); // マーカーを更新または作成
var latlngs = filteredData.map(function(element) {
return [element.latitude, element.longitude];
});
// ゼッケン番号に対応する色を取得
var color = getColorForZekken(zekken);
// 色を指定してポリライン(ルート)を作成し、地図に追加
var polyline = L.polyline(latlngs, { color: color }).addTo(map);
allPolylines[zekken].push(polyline);
} else {
// フィルタリングされたデータがない場合、既存のマーカーを削除
removeMarker(zekken);
}
});
// console.log(allPolylines);
} */
//当日用または近い日のデバッグ用
///当日用
function updateRoutesDisplay(minutesAgo) {
console.log(minutesAgo);
// 現在の日時から年、月、日を取得
var currentDate = new Date(); // UTC時刻に基づいた現在時刻
var year = currentDate.getFullYear();
var month = currentDate.getMonth(); // JavaScriptの月は0から始まるため、1月は0となります
var day = currentDate.getDate();
var hours = currentDate.getHours();
var minutes = currentDate.getMinutes();
var seconds = currentDate.getSeconds();
// イベントの終了時刻を設定16:00を終了時刻とする
//var endDate = new Date(year, month, day, 16, 20, 0); // 当日の16:00
// 最新のcreate_at時刻を格納する変数を初期化
var latestCreateTime = new Date(0); // 非常に古い日付で初期化
//var cutoffTime = new Date(endDate.getTime() - minutesAgo * 60000);
var cutoffTime = new Date(year, month, day, hours, minutes - minutesAgo, seconds);
// さらに20分遡った時刻を計算
var startTime = new Date(cutoffTime.getTime() - 20 * 60000);
document.getElementById('time-display').textContent = minutesAgo;
Object.keys(allRoutesData).forEach(function(zekken) {
var routeData = allRoutesData[zekken];
if (!allPolylines[zekken]) {
allPolylines[zekken] = [];
}
// 既存のポリラインをマップから削除
allPolylines[zekken].forEach(function(polyline) {
map.removeLayer(polyline);
});
allPolylines[zekken] = [];
var filteredData = routeData.filter(function(point) {
var pointTime = point.create_at.includes('-') ?
new Date(point.create_at) :
new Date(year, month, day, ...point.create_at.split(":").map(Number));
// startTime 以降、cutoffTime 以前のデータをフィルタリング
return pointTime >= startTime && pointTime <= cutoffTime;
});
// 最新のスポットを見つけます
if (filteredData.length > 0) {
var latestSpot = filteredData[filteredData.length - 1]; // 最新のスポット
updateMarker(zekken, latestSpot); // マーカーを更新または作成
var latlngs = filteredData.map(function(element) {
return [element.latitude, element.longitude];
});
// ゼッケン番号に対応する色を取得
var color = getColorForZekken(zekken);
// 色を指定してポリライン(ルート)を作成し、地図に追加
var polyline = L.polyline(latlngs, { color: color }).addTo(map);
allPolylines[zekken].push(polyline);
} else {
// フィルタリングされたデータがない場合、既存のマーカーを削除
removeMarker(zekken);
}
});
}
// マーカーを更新または作成する関数
function updateMarker(zekken, spot) {
var popupContent = "チーム名:" + zekken + "<br />緯度:" + spot.latitude + "<br />経度:" + spot.longitude;
if (marker[zekken]) {
marker[zekken].setLatLng([spot.latitude, spot.longitude]).setPopupContent(popupContent);
} else {
marker[zekken] = L.marker([spot.latitude, spot.longitude]).addTo(map).bindPopup(popupContent);
}
// ダブルクリックイベントリスナーをマーカーに追加
marker[zekken].on('dblclick', onMarkerdbClick);
}
// 既存のマーカーを削除する関数
function removeMarker(zekken) {
if (marker[zekken]) {
marker[zekken].remove();
delete marker[zekken];
}
}
function calculateTimeRange() {
var maxMinutesAgo = 300; // 300分前を最大の範囲とする
var slider = document.getElementById("time-slider");
slider.min = 0; // スライダーの最小値
slider.max = maxMinutesAgo; // スライダーの最大値を300分前に設定
slider.value = slider.max; // スライダーの値を最大値「0分前」に相当に設定
console.log(0)
updateRoutesDisplay(0); // 「0分前」の状態でルートを更新する
}
function parseDateTime(dateTimeStr) {
// 例えば、"2024-01-18 11:57:08" と "20:42:1" のようなフォーマットを処理
if (dateTimeStr.includes('-')) {
// "2024-01-18 11:57:08" のようなフォーマット
return new Date(dateTimeStr.replace(' ', 'T') + 'Z'); // ISO 8601フォーマットに変換
} else {
// "20:42:1" のような時刻だけのフォーマット
// 今日の日付に時刻を追加するなど、適宜処理を行う
let today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
return new Date(today + 'T' + dateTimeStr + 'Z');
}
}
// 緯度経度から距離を求める関数
// 参考https://qiita.com/kawanet/items/a2e111b17b8eb5ac859a
function distance(lat1, lng1, lat2, lng2) {
lat1 *= R;
lng1 *= R;
lat2 *= R;
lng2 *= R;
return 6371 * Math.acos(Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) + Math.sin(lat1) * Math.sin(lat2));
}
// 小数点以下を四捨五入する関数 桁数は10,100のような形で指定する小数点第一位なら10,小数点第二位なら100
// 参考https://qiita.com/nagito25/items/0293bc317067d9e6c560 (ただし説明が間違えている)
function orgRound(value, base) {
return Math.round(value * base) / base;
}
function showTimeInJapan() {
// 現在の日時をUTCとして取得
const now = new Date();
// 日本のタイムゾーンオフセットを取得UTC+9と仮定
const japanTimezoneOffset = 9 * 60 * 60 * 1000; // 9時間をミリ秒に変換
// UTCから日本の時差を加算して、日本の時刻を取得
const timeInJapan = new Date(now.getTime() + japanTimezoneOffset);
// 時間、分、秒を取得
const hours = timeInJapan.getUTCHours();
const minutes = timeInJapan.getUTCMinutes();
const seconds = timeInJapan.getUTCSeconds();
// 時刻を '時:分:秒' の形式でフォーマット
const formattedTime = [
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
].join(':');
// HTML内の指定された要素に時刻を設定
document.getElementById('japanTime').textContent = formattedTime;
}
// ページ読み込み時と1秒ごとに時刻を更新
window.onload = function() {
showTimeInJapan();
setInterval(showTimeInJapan, 1000); // 1000ミリ秒1秒ごとに更新
}
// 色に関するコードを色管理関連の関数として整理
function initializeColors() {
// 50色の色相リストを生成
var colors = [];
for (var i = 0; i < 50; i++) {
var hue = (360 / 50) * i; // 色相を計算
colors.push('hsl(' + hue + ', 100%, 50%)'); // 彩度100%、輝度50%で色を追加
}
var currentColorIndex = 0; // 現在の色のインデックス
window.getNextColor = function() {
var color = colors[currentColorIndex]; // 現在のインデックスの色を取得
currentColorIndex++; // インデックスをインクリメント
if (currentColorIndex >= colors.length) {
currentColorIndex = 0; // インデックスが配列の長さ以上になったら0にリセット
}
return color;
};
window.zekkenColors = {}; // ゼッケン番号ごとの色を格納するオブジェクト
}
function getColorForZekken(zekken) {
// すでにこのゼッケン番号に色が割り当てられている場合はそれを使用
if (zekkenColors[zekken]) {
return zekkenColors[zekken];
}
// 新しい色を割り当て
var color = getNextColor();
zekkenColors[zekken] = color; // zekkenColorsオブジェクトに色を追加
return color;
}
function fetchUserLocations() {
const eventCode = document.getElementById('event_select_for_user_narrow').value;
const zekkenNumber = document.getElementById('zekken_number_map').value;
// APIエンドポイントのURLを構築
const url = URL_base + `/fetchUserLocations?zekken_number=${zekkenNumber}&event_code=${eventCode}`;
// fetchを使用してリクエストを送信
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// 地図上にマーカーを配置する処理
console.log(data)
addMarkersToMap(data);
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
// 地図上にマーカーを配置し、既存のマーカーを取り除く関数
function addMarkersToMap(data) {
// まず、既存のマーカーを地図から全て削除
allMarkers.forEach(marker => {
map.removeLayer(marker);
});
// 配列をクリア
allMarkers = [];
// 新しいデータでマーカーを作成
data.forEach(location => {
const { latitude, longitude, cp_name: cpName, cp_number: cpNumber, create_at: createdAt } = location;
const marker = L.marker([latitude, longitude]).addTo(map);
// ポップアップにチェックポイント番号、チェックポイント名、緯度、経度、作成日時を表示
marker.bindPopup(`<b>CP No: ${cpNumber}</b><br><b>CP Name: ${cpName}</b><br>Latitude: ${latitude}<br>Longitude: ${longitude}`);
// 新しいマーカーを配列に追加
allMarkers.push(marker);
});
}
document.addEventListener('DOMContentLoaded', function() {
var defaultBtn = document.getElementById('defaultBehaviorButton');
var overrideBtn = document.getElementById('overrideBehaviorButton');
var mapButton = document.getElementById('map_button');
var zekken_number_in_input_area = document.getElementById('zekken_number_map');
zekken_number_in_input_area.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault(); // エンターキーのデフォルト動作を防止
// 必要ならここでカスタム動作を実行
console.log('エンターキーが押されましたが、フォームは送信されません。');
}
});
initializeMap();
setupEventListeners();
// デフォルトの挙動を設定するボタンのイベントリスナー
document.getElementById('defaultBehaviorButton').addEventListener('click', function() {
document.querySelector('.wap').style.display = 'block'; // wap を表示
document.querySelector('.wap2').style.display = 'none'; // wap2 を非表示
// オーバーライドボタンを活性化し、自身を非活性化
document.getElementById('overrideBehaviorButton').disabled = false;
this.disabled = true;
overrideBtn.style.display = 'none';
});
// click ボタンmap_buttonのイベントリスナー
mapButton.addEventListener('click', function() {
// 設定を適用してAPIを呼び出す
initializeColors();
loadAllRoutes();
startSliderCountdown();
mapButton.style.display = 'none';
});
// オーバーライドの挙動を設定するボタンのイベントリスナー
document.getElementById('overrideBehaviorButton').addEventListener('click', function() {
document.querySelector('.wap').style.display = 'none'; // wap を非表示
document.querySelector('.wap2').style.display = 'flex'; // wap2 を表示
// loadAllRoutes関数を新しい挙動にオーバーライド
loadAllRoutes = function() {
console.log('オーバーライドされた loadAllRoutes 関数が実行されました');
// 新しい処理をここに書く
const eventSelect = document.getElementById('event_select_for_user_narrow').value;
const zekkenNumber = document.getElementById('zekken_number_map').value;
// 取得した値を使用して何らかの処理を実行
// 例えば、これらの値を使ってAPIを呼び出すなど
console.log('選択されたイベント:', eventSelect);
console.log('入力されたゼッケン番号:', zekkenNumber);
fetchUserLocations(zekkenNumber, eventSelect)
};
// デフォルトボタンを活性化し、自身を非活性化
document.getElementById('defaultBehaviorButton').disabled = false;
this.disabled = true;
defaultBtn.style.display = 'none';
});
});
</script>
</body>
</html>