File size: 3,878 Bytes
8b96826
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Saarthi map layer — CARTO Positron tiles, crimson route with draw-in
// animation, custom pins, zoom + fit controls.
// Exposes window.SaarthiMap = { setPin, drawRoute, reset }

(function () {
  const LUCKNOW = [26.8467, 80.9462];

  const map = L.map('map', { zoomControl: false }).setView(LUCKNOW, 13);
  L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
    attribution: '© OpenStreetMap © CARTO',
    maxZoom: 19,
  }).addTo(map);

  L.control.zoom({ position: 'bottomright' }).addTo(map);

  let lastBounds = null;
  const FitControl = L.Control.extend({
    onAdd() {
      const btn = L.DomUtil.create('button', 'map-fit-btn');
      btn.innerHTML = '⌖';
      btn.title = 'Fit route';
      btn.setAttribute('aria-label', 'Fit route in view');
      L.DomEvent.on(btn, 'click', (event) => {
        L.DomEvent.stop(event);
        if (lastBounds) map.fitBounds(lastBounds, { padding: [70, 70] });
      });
      return btn;
    },
  });
  new FitControl({ position: 'bottomright' }).addTo(map);

  const pinIcon = (start) => L.divIcon({
    className: '',
    iconSize: [26, 26],
    iconAnchor: [13, 24],
    html: `<div class="pin-marker ${start ? 'pin-start' : ''}"><div class="pin-body"></div></div>`,
  });

  const pins = { from: null, to: null };
  let routeGroup = null;

  function setPin(kind, lat, lon, name) {
    if (pins[kind]) pins[kind].remove();
    pins[kind] = L.marker([lat, lon], { icon: pinIcon(kind === 'from') })
      .bindPopup(`<b>${kind === 'from' ? 'From' : 'To'}:</b> ${name}`)
      .addTo(map);

    if (pins.from && pins.to) {
      const bounds = L.latLngBounds(pins.from.getLatLng(), pins.to.getLatLng());
      lastBounds = bounds;
      map.fitBounds(bounds, { padding: [70, 70] });
    } else {
      map.setView([lat, lon], 14, { animate: true });
    }
  }

  // Animate an SVG polyline so the route "draws" itself along the path.
  function animateDraw(line, durationMs) {
    const path = line.getElement && line.getElement();
    if (!path || !path.getTotalLength) return;
    const length = path.getTotalLength();
    path.style.transition = 'none';
    path.style.strokeDasharray = `${length}`;
    path.style.strokeDashoffset = `${length}`;
    path.getBoundingClientRect(); // force reflow before transitioning
    path.style.transition = `stroke-dashoffset ${durationMs}ms ease-in-out`;
    path.style.strokeDashoffset = '0';
    setTimeout(() => { path.style.strokeDasharray = 'none'; }, durationMs + 120);
  }

  function drawRoute(origin, destination, route) {
    if (routeGroup) routeGroup.remove();
    const group = L.layerGroup();

    setPin('from', origin.lat, origin.lon, origin.name);
    setPin('to', destination.lat, destination.lon, destination.name);

    let bounds;
    if (route && route.points && route.points.length > 1) {
      const casing = L.polyline(route.points, { color: '#fff', weight: 10, opacity: 0.95 });
      casing.addTo(group);
      const line = L.polyline(route.points, { color: '#DC143C', weight: 5.5, opacity: 0.95 });
      line.addTo(group);
      bounds = line.getBounds();
      routeGroup = group.addTo(map);
      setTimeout(() => {
        animateDraw(casing, 1300);
        animateDraw(line, 1400);
      }, 250);
    } else {
      bounds = L.latLngBounds([origin.lat, origin.lon], [destination.lat, destination.lon]);
      routeGroup = group.addTo(map);
    }

    lastBounds = bounds;
    setTimeout(() => {
      map.invalidateSize();
      map.fitBounds(bounds, { padding: [70, 70] });
    }, 80);
  }

  function reset() {
    if (routeGroup) { routeGroup.remove(); routeGroup = null; }
    Object.keys(pins).forEach((kind) => {
      if (pins[kind]) { pins[kind].remove(); pins[kind] = null; }
    });
    lastBounds = null;
    map.setView(LUCKNOW, 13);
  }

  window.SaarthiMap = { setPin, drawRoute, reset };
})();