mirror of
https://github.com/morgan9e/KorailMap
synced 2026-04-13 16:04:07 +09:00
Fix
This commit is contained in:
351
index.html
351
index.html
@@ -11,165 +11,170 @@
|
|||||||
crossorigin=""></script>
|
crossorigin=""></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<style>
|
<style>
|
||||||
#map {
|
html, body {
|
||||||
height: calc(100vh - 60px);
|
height: 100%;
|
||||||
background-color: #e5e5e5;
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.station-label {
|
#map {
|
||||||
text-align: center;
|
height: 100%;
|
||||||
font-weight: bold;
|
}
|
||||||
font-size: 15px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.train-marker {
|
.station-label {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.bottombar {
|
.train-marker {
|
||||||
height: 40px;
|
text-align: center;
|
||||||
display: flex;
|
}
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottombar .left {
|
#reload {
|
||||||
display: flex;
|
padding: 5px 10px;
|
||||||
align-items: center;
|
background-color: #007BFF;
|
||||||
}
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.bottombar .left input[type="checkbox"] {
|
#reload:hover {
|
||||||
margin-right: 10px;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottombar .right {
|
.hidden {
|
||||||
display: flex;
|
display: none;
|
||||||
align-items: center;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.bottombar .loading {
|
#info {
|
||||||
margin-right: 15px;
|
position: fixed;
|
||||||
}
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
#reloadTrains {
|
z-index: 1000;
|
||||||
padding: 5px 10px;
|
}
|
||||||
background-color: #007BFF;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#reloadTrains:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div class="bottombar">
|
<div id="info">
|
||||||
<div class="left">
|
<button id="reload">Reload</button>
|
||||||
<input type="checkbox" id="layer0"> Express
|
<span id="loading">Loading...</span>
|
||||||
<input type="checkbox" id="layer1"> Semi
|
|
||||||
<input type="checkbox" id="layer2"> Normal
|
|
||||||
<input type="checkbox" id="layer3"> Passenger
|
|
||||||
<input type="checkbox" id="layer4"> Subway
|
|
||||||
<input type="checkbox" id="layer5"> Logis
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<span class="loading" id="loading">Loading...</span>
|
|
||||||
<button id="reloadTrains">Reload</button>
|
|
||||||
<input type="checkbox" id="autoReload"> Auto
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
const trainAPI = 'https://gis.korail.com/api/train?bbox=120.6263671875,28.07910949377748,134.0736328125,45.094739803960664'
|
||||||
|
const server = ''
|
||||||
|
|
||||||
|
let openPopupTrainId = null;
|
||||||
|
let followingTrainId = null;
|
||||||
|
let isFollowing = false;
|
||||||
|
|
||||||
async function myFetch(url, options = {}) {
|
async function myFetch(url, options = {}) {
|
||||||
let headers = options.headers
|
let headers = options.headers
|
||||||
let f = fetch('https://proxy.devpg.net', {
|
return fetch('https://proxy/', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Proxy-URL': url,
|
'X-Proxy-URL': url,
|
||||||
'X-Proxy-Header': JSON.stringify(headers),
|
'X-Proxy-Header': JSON.stringify(headers),
|
||||||
'X-Proxy-Cache': '0'
|
'X-Proxy-Cache': '0'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(`fetch(${url}, headers=${JSON.stringify(headers)}`, f);
|
|
||||||
return f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const trainAPI = 'https://gis.korail.com/api/train?bbox=120.6263671875,28.07910949377748,134.0736328125,45.094739803960664'
|
|
||||||
|
|
||||||
const server = ''
|
|
||||||
const map = L.map('map').setView([36.5, 128], 7.5);
|
const map = L.map('map').setView([36.5, 128], 7.5);
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
const railLayers = {
|
L.tileLayer('https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', {
|
||||||
express: L.layerGroup().addTo(map),
|
attribution: '© <a href="https://www.openrailwaymap.org/">OpenRailwayMap</a>',
|
||||||
normal: L.layerGroup().addTo(map),
|
maxZoom: 19
|
||||||
semi: L.layerGroup().addTo(map),
|
}).addTo(map);
|
||||||
logis: L.layerGroup().addTo(map)
|
|
||||||
};
|
|
||||||
|
|
||||||
const stationLayers = [];
|
const stationLayers = [];
|
||||||
|
|
||||||
for (let i = 0; i < 64; i++) {
|
for (let i = 0; i < 64; i++) {
|
||||||
const layerGroup = L.layerGroup().addTo(map);
|
stationLayers.push(L.layerGroup());
|
||||||
stationLayers.push(layerGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let layer of stationLayers) {
|
|
||||||
map.removeLayer(layer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const trainLayer = L.layerGroup().addTo(map);
|
const trainLayer = L.layerGroup().addTo(map);
|
||||||
|
const trainPositions = new Map();
|
||||||
|
|
||||||
function startSpinner() {
|
function startSpinner() {
|
||||||
document.getElementById("loading").classList.remove("hidden");
|
document.getElementById("loading").classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSpinner() {
|
function clearSpinner() {
|
||||||
document.getElementById("loading").classList.add("hidden");
|
document.getElementById("loading").classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadRail(type) {
|
function calculateDistance(lat1, lon1, lat2, lon2) {
|
||||||
fetch(`${server}/api/rail/${type}`)
|
const R = 6371;
|
||||||
.then(response => {
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
||||||
if (!response.ok) {
|
const dLon = (lon2 - lon1) * Math.PI / 180;
|
||||||
throw new Error(`Network response was not ok ${response.statusText}`);
|
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
||||||
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
||||||
|
Math.sin(dLon/2) * Math.sin(dLon/2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateAverageSpeed(trainId) {
|
||||||
|
const positions = trainPositions.get(trainId);
|
||||||
|
if (!positions || positions.length < 2) return null;
|
||||||
|
|
||||||
|
const firstChange = positions[0];
|
||||||
|
const lastChange = positions[positions.length - 1];
|
||||||
|
const totalDistance = calculateDistance(firstChange.lat, firstChange.lon, lastChange.lat, lastChange.lon);
|
||||||
|
const totalTime = (lastChange.timestamp - firstChange.timestamp) / 1000;
|
||||||
|
|
||||||
|
if (totalTime === 0) return null;
|
||||||
|
return (totalDistance / totalTime) * 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainPosition(trainId, lat, lon, timestamp) {
|
||||||
|
if (!trainPositions.has(trainId)) {
|
||||||
|
trainPositions.set(trainId, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const positions = trainPositions.get(trainId);
|
||||||
|
|
||||||
|
if (positions.length > 0) {
|
||||||
|
const lastPos = positions[positions.length - 1];
|
||||||
|
if (Math.abs(lastPos.lat - lat) < 0.0001 && Math.abs(lastPos.lon - lon) < 0.0001) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookbackCount = Math.min(12, positions.length);
|
||||||
|
for (let i = Math.max(0, positions.length - lookbackCount); i < positions.length; i++) {
|
||||||
|
const historicalPos = positions[i];
|
||||||
|
if (Math.abs(historicalPos.lat - lat) < 0.0001 && Math.abs(historicalPos.lon - lon) < 0.0001) {
|
||||||
|
const positionsBack = positions.length - 1 - i;
|
||||||
|
if (positionsBack > 0 && positionsBack <= 10) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return response.json();
|
}
|
||||||
})
|
}
|
||||||
.then(data => {
|
|
||||||
L.geoJSON(data, {
|
positions.push({ lat, lon, timestamp });
|
||||||
style: {
|
if (positions.length > 30) {
|
||||||
color: type === 'express' ? 'blue' : type === 'normal' ? 'green' : type === 'semi' ? 'orange' : 'red'
|
positions.shift();
|
||||||
}
|
}
|
||||||
}).addTo(railLayers[type]);
|
|
||||||
})
|
|
||||||
.catch(error => console.error(`Error fetching ${type} rail data: `, error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadStations() {
|
function loadStations() {
|
||||||
fetch(`${server}/api/station`)
|
fetch(`${server}/api/station`)
|
||||||
.then(response => {
|
.then(response => response.json())
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
L.geoJSON(data, {
|
L.geoJSON(data, {
|
||||||
pointToLayer: function (feature, latlng) {
|
pointToLayer: function (feature, latlng) {
|
||||||
const shownLayer = parseInt(feature.properties.shown_layer.slice(2), 2);
|
const shownLayer = parseInt(feature.properties.shown_layer.slice(2), 2);
|
||||||
|
|
||||||
const marker = L.marker(latlng, {
|
const marker = L.marker(latlng, {
|
||||||
icon: L.divIcon({
|
icon: L.divIcon({
|
||||||
className: 'station-label',
|
className: 'station-label',
|
||||||
@@ -180,7 +185,6 @@
|
|||||||
if (stationLayers[shownLayer]) {
|
if (stationLayers[shownLayer]) {
|
||||||
stationLayers[shownLayer].addLayer(marker);
|
stationLayers[shownLayer].addLayer(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -190,35 +194,47 @@
|
|||||||
|
|
||||||
function loadTrains() {
|
function loadTrains() {
|
||||||
startSpinner();
|
startSpinner();
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
let wasPopupOpen = false;
|
||||||
|
if (map._popup && map._popup._source) {
|
||||||
|
const popup = map._popup;
|
||||||
|
if (popup._source.options && popup._source.options.trainId) {
|
||||||
|
openPopupTrainId = popup._source.options.trainId;
|
||||||
|
wasPopupOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
myFetch(trainAPI, {
|
myFetch(trainAPI, {
|
||||||
headers: {
|
headers: {
|
||||||
"x-requested-with": "com.korail.talk",
|
"x-requested-with": "com.korail.talk",
|
||||||
"referer": "https://gis.korail.com/korailTalk/entrance",
|
"referer": "https://gis.korail.com/korailTalk/entrance",
|
||||||
"user-agent": "korailtalk AppVersion/6.3.3"
|
"user-agent": "korailtalk AppVersion/6.3.3"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => response.json())
|
||||||
if (!response.ok) {
|
.then(data => {
|
||||||
throw new Error('Network response was not ok');
|
trainLayer.clearLayers();
|
||||||
}
|
let markerToReopen = null;
|
||||||
return response.json();
|
let markerToFollow = null;
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
trainLayer.clearLayers();
|
|
||||||
|
|
||||||
L.geoJSON(data, {
|
L.geoJSON(data, {
|
||||||
pointToLayer: function (feature, latlng) {
|
pointToLayer: function (feature, latlng) {
|
||||||
|
const trainId = `${feature.properties.trn_case}_${feature.properties.trn_no}`;
|
||||||
|
|
||||||
|
updateTrainPosition(trainId, latlng.lat, latlng.lng, timestamp);
|
||||||
|
|
||||||
|
const avgSpeed = calculateAverageSpeed(trainId);
|
||||||
|
const speedText = avgSpeed ? `${avgSpeed.toFixed(1)} km/h` : 'N/A';
|
||||||
|
|
||||||
|
const followButtonText = followingTrainId === trainId ? 'Unfollow' : 'Follow';
|
||||||
|
const followButtonColor = followingTrainId === trainId ? '#dc3545' : '#28a745';
|
||||||
|
|
||||||
const trainIconHtml = `
|
const trainIconHtml = `
|
||||||
<div style="position: relative; text-align: center;">
|
<div style="position: relative; text-align: center;">
|
||||||
<div style="background-color: #007bff;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
display: inline-block;
|
|
||||||
border: 2px solid white;"></div>
|
|
||||||
<div style="font-size: 12px;
|
<div style="font-size: 12px;
|
||||||
color: black;
|
color: ${feature.properties.trn_case === "SRT" ? 'white' : 'black'};
|
||||||
background-color: white;
|
background-color: ${feature.properties.trn_case === "SRT" ? '#572b4c' : '#007bff'};
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding: 1px 3px;
|
padding: 1px 3px;
|
||||||
@@ -229,61 +245,72 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
white-space: nowrap;">${feature.properties.trn_case} ${feature.properties.trn_no}</div>
|
white-space: nowrap;">${feature.properties.trn_case} ${feature.properties.trn_no}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
return L.marker(latlng, {
|
|
||||||
|
const marker = L.marker(latlng, {
|
||||||
icon: L.divIcon({
|
icon: L.divIcon({
|
||||||
className: 'train-marker',
|
className: 'train-marker',
|
||||||
html: trainIconHtml,
|
html: trainIconHtml,
|
||||||
iconSize: [30, 42],
|
iconSize: [30, 42],
|
||||||
iconAnchor: [15, 21]
|
iconAnchor: [15, 21]
|
||||||
})
|
}),
|
||||||
|
trainId: trainId
|
||||||
}).bindPopup(`${feature.properties.trn_case} ${feature.properties.trn_no}<br>
|
}).bindPopup(`${feature.properties.trn_case} ${feature.properties.trn_no}<br>
|
||||||
(${feature.properties.dpt_stn_nm} -> ${feature.properties.arv_stn_nm})<br>
|
(${feature.properties.dpt_stn_nm} -> ${feature.properties.arv_stn_nm})<br>
|
||||||
${feature.properties.now_stn} ${feature.properties.next_stn}`);
|
${feature.properties.now_stn} ${feature.properties.next_stn}<br>
|
||||||
|
<strong>Avg Speed: ${speedText}</strong><br>
|
||||||
|
<button onclick="toggleFollow('${trainId}')"
|
||||||
|
style="background-color: ${followButtonColor};
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 5px;">${followButtonText}</button>`);
|
||||||
|
|
||||||
|
if (wasPopupOpen && trainId === openPopupTrainId) {
|
||||||
|
markerToReopen = marker;
|
||||||
}
|
}
|
||||||
}).addTo(trainLayer);
|
|
||||||
clearSpinner();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching train data: ', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRail('express');
|
if (isFollowing && trainId === followingTrainId) {
|
||||||
loadRail('normal');
|
markerToFollow = marker;
|
||||||
loadRail('semi');
|
|
||||||
loadRail('logis');
|
|
||||||
|
|
||||||
|
|
||||||
for (let f = 0; f < 6; f++) {
|
|
||||||
document.getElementById(`layer${f}`).addEventListener('change', function () { // lable0: 0b 1XXXXX, label1: 0bX1XXXX, ...
|
|
||||||
for (let i = 0; i < 64; i++) {
|
|
||||||
if ( i & (1 << (5 - 0)) ) {
|
|
||||||
if (this.checked) {
|
|
||||||
map.addLayer(stationLayers[i]);
|
|
||||||
} else {
|
|
||||||
map.removeLayer(stationLayers[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
}
|
}
|
||||||
|
}).addTo(trainLayer);
|
||||||
|
|
||||||
|
if (markerToReopen) {
|
||||||
|
setTimeout(() => markerToReopen.openPopup(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (markerToFollow) {
|
||||||
|
setTimeout(() => map.setView(markerToFollow.getLatLng(), map.getZoom()), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSpinner();
|
||||||
})
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching train data: ', error);
|
||||||
|
clearSpinner();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('reloadTrains').addEventListener('click', loadTrains);
|
function toggleFollow(trainId) {
|
||||||
document.getElementById('autoReload').addEventListener('click', autoReload);
|
if (followingTrainId === trainId) {
|
||||||
|
followingTrainId = null;
|
||||||
let reloadInterval = null
|
isFollowing = false;
|
||||||
function autoReload() {
|
} else {
|
||||||
if (this.checked) {
|
followingTrainId = trainId;
|
||||||
reloadInterval = setInterval(loadTrains, 2000);
|
isFollowing = true;
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (reloadInterval) {
|
|
||||||
clearInterval(reloadInterval);
|
|
||||||
reloadInterval = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
loadTrains();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('reload').addEventListener('click', loadTrains);
|
||||||
|
|
||||||
loadStations();
|
loadStations();
|
||||||
loadTrains();
|
loadTrains();
|
||||||
|
setInterval(loadTrains, 2000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
5025
json/trains.json
5025
json/trains.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user