MapEvents.res

  1let fetchFeatureStats = (
  2  f: Maplibre.feature,
  3  event: Maplibre.mapLayerMouseEvent,
  4  waypoints: array<Waypoint.attributes>,
  5): promise<option<Jsx.element>> => {
  6  let hash = f.source->String.replace("route-", "")
  7  Log.debug(`[MapInstance] Got mouseover hash ${hash}`)
  8
  9  let url = `/route/${hash}.geojson`
 10  Log.debug(`[MapInstance] Fetching from URL: ${url}`)
 11
 12  Fetch.get(url)
 13  ->Fetch.onResponse(
 14    ~onSuccess=json => {
 15      Log.debug(`[MapInstance] Successfully fetched geojson for ${hash}`)
 16      let stats = Geo.getStats(event.lngLat->Geo.lngLatToPoint, hash, waypoints, json->Obj.magic)
 17      Console.log2("[MapInstance] Calculated stats:", stats)
 18      Some(<Stats dist={stats.dist} time={stats.time} waypoints={waypoints} />)
 19    },
 20    ~onError=(status, msg) => {
 21      Log.error(
 22        `[MapInstance] Failed to fetch geojson for ${hash}`,
 23        ~kv={
 24          "status": status->Int.toString,
 25          "message": msg,
 26          "url": url,
 27        },
 28      )
 29      None
 30    },
 31  )
 32  ->Fetch.send
 33}
 34
 35let getStats = (
 36  features: array<Maplibre.feature>,
 37  event: Maplibre.mapLayerMouseEvent,
 38  waypoints: array<Waypoint.attributes>,
 39  popup: Preact.Signal.t<option<Jsx.element>>,
 40) => {
 41  Log.debug(`[MapInstance] Fetching stats for ${features->Array.length->Int.toString} features`)
 42
 43  // Fetch all features concurrently
 44  let promises = features->Array.map(f => fetchFeatureStats(f, event, waypoints))
 45  Array.reverse(promises)
 46
 47  let _ = (
 48    async () => {
 49      let results = await Promise.all(promises)
 50
 51      // Filter out None results (failed fetches)
 52      let statsElements = results->Array.filterMap(x => x)
 53
 54      if statsElements->Array.length > 0 {
 55        Log.debug(
 56          `[MapInstance] Successfully loaded ${statsElements
 57            ->Array.length
 58            ->Int.toString} of ${features->Array.length->Int.toString} stats`,
 59        )
 60
 61        popup->Preact.Signal.set(
 62          Some(
 63            <Maplibre.Popup
 64              longitude={event.lngLat.lng}
 65              latitude={event.lngLat.lat}
 66              onClose={_evt => popup->Preact.Signal.set(None)}
 67              closeButton={false}
 68              maxWidth={Window.isMobile->Preact.Signal.get ? "300px" : "600px"}
 69            >
 70              <div className="flex flex-col">
 71                {switch statsElements {
 72                | [stat] => stat
 73                | stats =>
 74                  stats
 75                  ->Array.mapWithIndex((elem, i) =>
 76                    <div className="mb-2">
 77                      <h3 className="font-bold text-[var(--color-blue)] mb-0">
 78                        {("Leg " ++ (i + 1)->Int.toString)->Preact.string}
 79                      </h3>
 80                      elem
 81                    </div>
 82                  )
 83                  ->Preact.array
 84                }}
 85              </div>
 86            </Maplibre.Popup>,
 87          ),
 88        )
 89      } else {
 90        Log.warn("[MapInstance] All feature stats failed to load")
 91      }
 92    }
 93  )()
 94}
 95
 96let handleDistance = (
 97  ~event: Maplibre.mapLayerMouseEvent,
 98  ~popup: Preact.Signal.t<option<Jsx.element>>,
 99  ~waypoints: array<Waypoint.attributes>,
100) => {
101  Log.debug("[MapInstance] Mouse enter event")
102  switch event.features {
103  | Some(features) =>
104    // Clear existing popup first to prevent stale data
105    popup->Preact.Signal.set(None)
106    // Fetch stats for all features concurrently
107    getStats(features, event, waypoints, popup)
108  | None =>
109    Log.debug("[MapInstance] No features on mouse enter")
110    popup->Preact.Signal.set(None)
111  }
112}