Get started
Show itinerary data

List segments and waypoints

List segments and waypoints

Three elements read one itinerary. ItinerarySegmentTable states the legs its author drew, and ItineraryWaypointTable states the places it reaches. WaypointCard opens one of those places in full.

No map required

All three resolve through the same cache as the elevation profile, so three elements naming one id cost one request. Install the two packages first: see install the SDK.

Choose between legs and places

A segment is what the itinerary's author drew: a name, a colour, a mode, and a duration the server computed. A hut-to-hut tour reads as legs. Day one is 17 km and 1,080 m of climb.

A waypoint is what they marked: a set of services, a cut-off, an arrival band, and whatever else the author published about the place. A race course reads as places. Les Contamines is at km 31, it has water and a hot meal, and it closes at 01:15.

Neither is a degenerate case of the other, which is why one table cannot serve both.

List the legs of an itinerary

import { ItinerarySegmentTable } from "@mountaya/elements-react/elements";

<ItinerarySegmentTable itineraryId="itin_abc" />;

One prop is the whole integration: the chain, the geometry and the server's figures arrive together, with each group already collapsed. A caller who already holds the legs passes segments instead, mapped by itinerarySegments on @mountaya/elements/data.

A row is a segment, in the order the itinerary runs, and a collapsed group is one row. Every measuring cell states two figures: this leg's, then the running total under it. Both tables publish Toolbar, Row and Sparkline as properties on themselves, and the Props tab lists what those take.

Neither table sorts. Reordering the rows would make both the distance delta and the lit sparkline wrong.

Add a column of your own

Custom columns go in the same ordered list the built-in ones do, so you reorder, drop and interleave them.

<ItinerarySegmentTable
  columns={[
    "segment",
    "profile",
    "distance",
    "elevation",
    {
      align: "end",
      cell: (row) => cutoffAt(row.id),
      header: "Cut-off time",
      id: "cutoff",
    },
  ]}
  itineraryId="itin_abc"
/>;

A cell callback receives the whole row and the units the built-in cells draw in. row.id is the leg's own id, or the group's for a collapsed group. Key your own data by it.

segment, profile, distance and elevation are on by default. altitude, duration and difficulty ship off, because each is empty on an itinerary that does not state it.

List the places of an itinerary

import { ItineraryWaypointTable } from "@mountaya/elements-react/elements";

<ItineraryWaypointTable itineraryId="itin_abc" />;

A row is a place, in the order the itinerary reaches them. The distance column states the stretch that reaches the place, then the running total. A caller who already holds the places passes waypoints and geojson, mapped by itineraryWaypoints and itineraryGeometry on the same module.

A place the itinerary doubles back past gets a row per pass. Rows are keyed ${id}#${pass}, so row.id is not unique across rows: key your own children by row.key and look your data up by row.id.

Choose the waypoint columns

KeyWhat it states
waypointThe place's icon and name, and the row's heading wherever you put it.
profileThe whole itinerary, with the reaching stretch lit.
distanceMeters from the start, over the stretch that reaches the place.
elevationClimb and loss since the previous place.
altitudeThe altitude at the place, one reading rather than a band.
amenitiesThe services the place offers, as badges.
cutoffWhen the place must be left.
arrivalThe published fastest to slowest arrival band.
propertiesThe author's own fields, in the format each declares.

On by default: waypoint, profile, distance, elevation, amenities. The other four ship off for the same reason the segment table's do.

<ItineraryWaypointTable
  columns={["waypoint", "distance", "amenities", "cutoff"]}
  itineraryId="itin_abc"
/>;

Filter the places by service

Pass onFilterChange and the toolbar grows a chip per service the itinerary offers, never the full vocabulary greyed out. The filter is controlled, so it can live in a URL.

import { useState } from "react";

import { ItineraryWaypointTable } from "@mountaya/elements-react/elements";

const Course = () => {
  const [filter, setFilter] = useState<readonly string[]>([]);

  return (
    <ItineraryWaypointTable filter={filter} itineraryId="itin_abc" onFilterChange={setFilter} />
  );
};

Selecting several services is an AND: picking water and hot-meal asks where you can do both.

A cut-off is published twice. cutoffOffset is elapsed seconds from the start and prints with no help. Formatting the wall-clock cutoffAt needs your application's locale and time zone, so pass a formatter for that spelling.

import { ItineraryWaypointTable } from "@mountaya/elements-react/elements";

<ItineraryWaypointTable
  columns={["waypoint", "distance", "cutoff"]}
  formatCutoffAt={(iso: string) =>
    new Intl.DateTimeFormat("fr-FR", {
      hour: "2-digit",
      minute: "2-digit",
      timeZone: "Europe/Paris",
    }).format(new Date(iso))
  }
  itineraryId="itin_abc"
/>;

Without one, the cutoff column falls back to the elapsed spelling. WaypointCard takes the same prop on the same terms.

Show one place in a card

WaypointCard states what a place is called, what it offers, when it can be used, and how to reach it. It is what a reader meets when they click a pin.

import { itineraryWaypoint } from "@mountaya/elements/data";
import { WaypointCard } from "@mountaya/elements-react/elements";

const mapped = itineraryWaypoint(record);

mapped === null ? null : <WaypointCard waypoint={mapped} />;

A Waypoint is what an element draws, which is not what an itinerary stores. itineraryWaypoint maps one onto the other, and the two tables use the same mapping. A waypoint with no geometry maps to null and draws nothing.

Every section renders only when it holds something: the services, the timing, the contact rows, the photos and links, then the author's own fields. A place that records nothing says so in one line.

The line under the name is the author's description where there is one. With none, it is assembled from what the place states: whether it is equipped, and when it can be used. withPhotos={false} drops the photo grid and keeps the links.

Draw your own markup

useWaypointCard derives everything the card states and renders nothing: the same order, the same fallbacks, the same units, the same assembled line.

import { useWaypointCard } from "@mountaya/elements-react/elements";

const Place = () => {
  const card = useWaypointCard({ waypoint });

  return (
    <section>
      <h3>{waypoint.name}</h3>
      <p>{card.headline}</p>
      <ul>
        {card.amenities.map((amenity) => (
          <li key={amenity.amenity}>{amenity.name}</li>
        ))}
      </ul>
    </section>
  );
};

amenity is the wire's own value, kept beside the name for your own glyph lookup. empty reports a place that states no facts.

For lists a reader edits rather than reads, SegmentList and WaypointList ship with the studio chrome.

Put a map on the provider and both tables find it.

<MountayaElementsProvider client={client} map={map}>
  <MapView client={client} onReady={setMap} terrain="topo" />
  <ItinerarySegmentTable itineraryId={itineraryId} />
</MountayaElementsProvider>

Hovering a row lights that leg on the route and pins its end. Hovering the route lights the row and scrolls it into view. Both directions are the default, and onHover suppresses them together. State highlighted instead and the table lights the row you name.

Each element uses a marker id of its own, so all three can sit over one map. markerId renames it.

Below roughly 44rem of the element's own width, not the viewport's, each row becomes a stacked card, custom columns included.

Icons, strings and theme are set once on the provider: see customize the elements.

Next steps