TimeZoneGPXAPI example

by Mapycz

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Simple map in Leaflet</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-sA+zWATbFveLLNqWO2gtiw3HL/lh1giY/Inf1BJ0z14=" crossorigin="" />
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha256-o9N1jGDZrf5tS+Ft4gbIK7mYMipq9lqpVJ91xHSyKhg=" crossorigin=""></script>
  </head>
  <body>
    <div class="app">
    <div id="map"></div>
    <aside>
      <h1>Activities (sorted by UTC)</h1>
      <div class="hint">
        Displaying <b>local time</b> and <b>UTC offset</b> at start location. Order is determined by <b>UTC time</b>.
      </div>

      <button class="zoom-all-btn" onclick="zoomToAll()">πŸ“ Show all activities</button>

      <div id="list" class="list" aria-live="polite"></div>
    </aside>
  </div>
  </body>
</html>

CSS

* { box-sizing: border-box; }
    body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; color: #111; }
    .app {
      display: grid;
      grid-template-columns: 1fr 360px;
      grid-template-rows: 100vh;
      width: 100vw; height: 100vh;
    }
    #map { width: 100%; height: 100%; }

    aside {
      border-left: 1px solid #e5e7eb;
      background: #fafafa;
      padding: 12px;
      overflow: auto;
    }
    h1 { font-size: 16px; margin: 0 0 8px; }
    .hint { font-size: 12px; color: #555; margin-bottom: 12px; }

    .list { display: flex; flex-direction: column; gap: 8px; }
    .item {
      background: #fff;
      border: 1px solid #e5e7eb;
      border-radius: 10px;
      padding: 10px 12px;
      cursor: pointer;
      transition: box-shadow .15s ease, border-color .15s ease;
    }
    .item:hover { box-shadow: 0 2px 12px rgba(0,0,0,.06); border-color: #d1d5db; }
    .item.active { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.15); }
    .row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
    .name { font-weight: 600; }
    .meta { font-size: 12px; color: #444; }
    .tz { font-size: 12px; color: #6b7280; }


    .zoom-all-btn {
      background: #2563eb;
      color: white;
      border: none;
      border-radius: 8px;
      padding: 8px 16px;
      font-size: 14px;
      font-weight: 600;
      cursor: pointer;
      margin-bottom: 12px;
      width: 100%;
      transition: background-color .15s ease;
    }
    .zoom-all-btn:hover { background: #1d4ed8; }
    .zoom-all-btn:active { background: #1e40af; }

    @media (max-width: 900px) {
      .app { grid-template-columns: 1fr 280px; grid-template-rows: 100vh; }
      aside { width: 280px; }
    }
    
    @media (max-width: 600px) {
      .app { grid-template-columns: 1fr 240px; grid-template-rows: 100vh; }
      aside { width: 240px; font-size: 14px; }
     ...

JavaScript

// replace with your own API keyyyy
const API_KEY =
  "eyJpIjoyNTcsImMiOjE2Njc0ODU2MjN9.c_UlvdpHGTI_Jb-TNMYlDYuIkCLJaUpi911RdlwPsAY"

// === MAIN TUTORIAL FUNCTION ===
    // This demonstrates the complete workflow of timezone-aware GPX activity processing
    async function runTimeZoneDemo() {
      console.log('🌍 Starting TimeZone Demo...');

      // Step 1: Initialize map and draw activity routes
      console.log('πŸ“ Step 1: Initialize map and draw routes');
      const map = initializeMap();
      const layerManager = drawActivityRoutes(map, activities);

      // Step 2: Fetch timezone information for all activities
      console.log('πŸ• Step 2: Fetch timezone data for start locations');
      const activitiesWithTimezones = await enrichActivitiesWithTimezones(activities);

      // Step 3: Sort activities by UTC time (not local time!)
      console.log('πŸ“Š Step 3: Sort activities by UTC time');
      const sortedActivities = sortActivitiesByUtc(activitiesWithTimezones);

      // Step 4: Render the activity list with local times
      console.log('πŸ“‹ Step 4: Render activity list');
      renderActivityList(sortedActivities, layerManager);

      // Step 5: Show all activities on map by default
      console.log('πŸ—ΊοΈ Step 5: Display all activities');
      zoomToAllActivities(layerManager);

      console.log('βœ… Demo completed! Key insight: Activities sorted by UTC, not local time.');
      return { map, layerManager, activities: sortedActivities };
    }

    // === Helper Functions ===
    function pad2(n){ return String(n).padStart(2,'0'); }
    function formatUtcOffset(offsetSeconds) {
      const sign = offsetSeconds >= 0 ? '+' : '-';
      const abs = Math.abs(offsetSeconds);
      const h = Math.floor(abs / 3600);
      const m = Math.floor((abs % 3600) / 60);
      return `UTC${sign}${pad2(h)}:${pad2(m)}`;
    }
    function formatLocal(dt, locale='en-GB') {
      // format: DD. MM. YYYY HH:mm
      // NOTE:...