const CLIENT_ID = '163773';
let accessToken = '';
let latlngs = [];
let activities = [];
let activityIndex = 0;
// Detect access token from redirect
const params = new URLSearchParams(window.location.search);
//-------------------------------------------------------------------------------
const showMap = (activity) => {
document.getElementById("login").style.display = "none";
document.getElementById("map").style.display = "block";
const API_KEY = 'Dp0Y4WgtEgqMzlXttJnbX9GAg2Ijj9lZUaFawE466Gk';
const map = L.map('map').setView(activity.latlngs[0], 16);
L.tileLayer(`https://api.mapy.com/v1/maptiles/outdoor/256/{z}/{x}/{y}?apikey=${API_KEY}`, {
minZoom: 0,
maxZoom: 19,
attribution: '© Seznam.cz a.s. a další',
}).addTo(map);
const LogoControl = L.Control.extend({
options: { position: 'bottomleft' },
onAdd: function (map) {
const container = L.DomUtil.create('div');
const link = L.DomUtil.create('a', '', container);
link.setAttribute('href', 'http://mapy.com/');
link.setAttribute('target', '_blank');
link.innerHTML = '
';
L.DomEvent.disableClickPropagation(link);
return container;
},
});
new LogoControl().addTo(map);
const StravaLogoControl = L.Control.extend({
options: { position: 'bottomright' },
onAdd: function (map) {
const container = L.DomUtil.create('div');
const img = L.DomUtil.create('img', '', container);
img.src = '/api_logo_pwrdBy_strava_stack_orange.png';
img.style.width = '100px'; // optional size control
//img.style.opacity = '0.8'; // optional transparency
return container;
},
});
new StravaLogoControl().addTo(map);
const polyline = L.polyline(activity.latlngs, { color: 'blue', weight: 5, opacity: 0.6, dashArray: [10, 10] })
.addTo(map);
// Create the starting marker with custom icon
const startMarker = L.marker(activity.latlngs[0], {}).addTo(map);
startMarker.bindPopup("Start: " + formatDate(activity.start_date_local));
startMarker.setOpacity(0.8);
// Create the ending marker with custom icon
const endMarker = L.marker(activity.latlngs[activity.latlngs.length - 1], {}).addTo(map);
const startDate = new Date(activity.start_date_local);
endMarker.bindPopup("End: " + formatDate(new Date(startDate.getTime() + activity.elapsed_time * 1000)));
endMarker.setOpacity(0.8);
map.fitBounds(polyline.getBounds());
// Create arrows
if (this.activityIndex > 0) {
const arrowLeft = L.DomUtil.create('div', 'arrow-control right-arrow', map.getContainer());
arrowLeft.innerHTML = '▶'; // ▶
arrowLeft.title = getInfoTextShort(this.activities[this.activityIndex-1]);
arrowLeft.onclick = () => onSwitchActivity(this.activityIndex-1, map);
}
if (this.activityIndex < this.activities.length - 1) {
const arrowRight = L.DomUtil.create('div', 'arrow-control left-arrow', map.getContainer());
arrowRight.innerHTML = '◀'; // ◀
arrowRight.title = getInfoTextShort(this.activities[this.activityIndex+1]);
arrowRight.onclick = () => onSwitchActivity(this.activityIndex+1, map);
}
// info text
const infoText = L.DomUtil.create('div', 'info-text', map.getContainer());
infoText.innerHTML = getInfoText(activity);
// Add click handler to open a new tab
/*infoText.onclick = () => {
window.open('https://www.strava.com/activities/'+activity.id, '_blank');
};*/
// Combo-box
const selectBox = L.DomUtil.create('select', 'activity-selector', map.getContainer());
this.activities.forEach((act, i) => {
const option = document.createElement('option');
option.value = i;
option.text = getInfoTextShort(act);
if (act.id === activity.id) {
option.selected = true;
}
selectBox.appendChild(option);
});
selectBox.onchange = (e) => {
const selectedIndex = parseInt(e.target.value, 10);
onSwitchActivity(selectedIndex, map);
};
// Change URL without reloading
window.history.pushState({}, '', `/activities/${activity.id}`);
/*const gpxUrl = 'track.gpx';
new L.GPX(gpxUrl, {
async: true,
marker_options: {
startIconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-icon-start.png',
endIconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-icon-end.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/pin-shadow.png'
}
}).on('loaded', function(e) {
map.fitBounds(e.target.getBounds());
}).addTo(map);*/
};
async function onSwitchActivity(selectedIndex, map) {
if (map) {
map.remove();
}
// Remove info-text and activity-selector from the DOM
const infoText = document.querySelector('.info-text');
if (infoText) {
infoText.remove();
}
const activitySelector = document.querySelector('.activity-selector');
if (activitySelector) {
activitySelector.remove();
}
const arrowLeft = document.querySelector('.left-arrow');
if (arrowLeft) {
arrowLeft.remove();
}
const arrowRight = document.querySelector('.right-arrow');
if (arrowRight) {
arrowRight.remove();
}
this.activityIndex = selectedIndex;
const mapContainer = document.getElementById("map");
mapContainer.style.display = "none";
let activity = this.activities[selectedIndex];
activity.latlngs = await downloadGpx(activity.id);
showMap(activity);
}
//------------------------------------------------------------------------
// Function to format distance in kilometers (max 2 decimal places)
function formatDistance(distance) {
return (distance / 1000).toFixed(2); // Convert meters to kilometers and round to 2 decimals
}
// Function to convert start date to local time without trailing seconds
function formatDate(dateStr) {
const date = new Date(dateStr);
return date.toLocaleString().replace(/:\d{2}$/, ''); // Remove seconds
}
// ------------------------------------------------------------
function getActivityIcon(type) {
if (type === 'Run') return "🏃";
if (type === 'Ride') return "🚴";
return "";
}
function getInfoText(activity) {
return ` ${activity.name}
${formatDate(activity.start_date_local)} – ${getActivityIcon(activity.type)} ${activity.type}, ${formatDistance(activity.distance)} km`
}
function getInfoTextShort(activity) {
return `${formatDate(activity.start_date_local)}, ${getActivityIcon(activity.type)} ${activity.name}, ${formatDistance(activity.distance)} km`
}
const getActivityIdFromURL = () => {
const path = window.location.pathname;
const match = path.match(/^\/activities\/([^\/]+)$/);
if (match) {
const activityId = match[1];
console.log("Activity ID:", activityId);
return activityId
} else {
return ''
}
}
const init = async () => {
try {
const code = params.get("code");
// Check if accessToken is already stored in localStorage
const storedToken = localStorage.getItem('accessToken');
if (storedToken) {
console.log("Using stored Strava token:", storedToken);
accessToken = storedToken; // Use the stored access token
await fetchActivities();
} else if (code) {
console.log("Strava code:", code);
const res = await fetch("https://api.strava-mapy.com/exchange-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code }),
});
const data = await res.json();
console.log("Strava token:", data.access_token);
accessToken = data.access_token;
// Store the access token in localStorage
localStorage.setItem('accessToken', accessToken);
// reflect activityId in URL
const activityId = params.get("state"); //activityId
redirectToBase(activityId ? `/activities/${activityId}`:'');
} else {
document.getElementById("login").style.display = "block";
document.getElementById("strava-connect").href =
`https://www.strava.com/oauth/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(window.location.origin + window.location.pathname)}&approval_prompt=auto&scope=read,activity:read,activity:read_all&state=${getActivityIdFromURL()}`;
}
} catch (error) {
console.error("Error occurred during initialization:", error);
}
};
function redirectToBase(activitySuffix='') {
window.location.href = window.location.origin + activitySuffix;
}
//----------------------------------------------------------
async function fetchFromStrava(url) {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.status === 401) {
goToLoginPage();
}
if (!response.ok) {
console.log('Error fetching activities');
return;
}
return await response.json()
}
//------------------------------------------------------------------------------
async function fetchActivities() {
let data = await fetchFromStrava("https://www.strava.com/api/v3/athlete/activities?per_page=20&page=1");
this.activities = data.filter(activity => activity.map && activity.map.summary_polyline);
if(this.activities.length===0) {
alert("Sorry, no recent activity with map found.");
return;
}
const id=getActivityIdFromURL();
let index=0;
if(id) {
index=this.activities.findIndex(activity => activity.id==id)
}
if(index==-1) {
data=await fetchFromStrava("https://www.strava.com/api/v3/activities/"+id);
if(!data || data.length==0) {
console.error("No activity with ID "+id);
return;
}
index=this.activities.push(data)-1;
}
onSwitchActivity(index, null)
/*
this.activities[0].latlngs = await downloadGpx(this.activities[0].id);
showMap(this.activities[0])*/
}
// ------------------------------------------------
function goToLoginPage() {
console.error("Invalid access token. Redirecting to the login page.")
localStorage.removeItem('accessToken');
redirectToBase();
}
//-----------------------------------------------------------------------------------------------
async function downloadGpx(activityId) {
const url = `https://www.strava.com/api/v3/activities/${activityId}/streams?keys=latlng&key_by_type=true`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.status === 401) {
goToLoginPage();
}
if (!response.ok) {
alert("Failed to download track data.");
return;
}
const data = await response.json();
return data.latlng.data;
}
//-------------------------------------------------------------------------------------
init()
.then(() => {
console.log("Init completed successfully");
})
.catch((error) => {
console.error("Error in init:", error);
});