openbeta-graphql icon indicating copy to clipboard operation
openbeta-graphql copied to clipboard

multipitch routes

Open musoke opened this issue 2 years ago • 19 comments

Is there a plan for how multi-pitch routes will be handled? I don't see much documentation of this.

#252 has some discussion. The suggestion there is that multipitch routes should get an array of bolt counts, one for each pitch.

Presumably the same would work for grades. Sandbag grades are ordered, so the overall route grade can be calculated as the maximum over the pitches. There will be more subtlety when different pitches have different disciplines (e.g. free and ice on one climb).

What other fields need generalizing?

@bradleyDean asks about variations in #252. Simplest suggestion is to list them as separate routes.

musoke avatar Apr 19 '23 07:04 musoke

As in https://github.com/OpenBeta/openbeta-graphql/pull/252 I suggest

  • adding new field "bolts" with String array that contains no of bolts per pitch
  • changing "grade" to String array to include difficulty per pitch
  • (and route variants are represented as individual routes, this preserves the option to tick routes)

This should be a user-friendly solution to represent multipitches.

l4u532 avatar Jun 05 '23 06:06 l4u532

  • (and route variants are represented as individual routes, this preserves the option to tick routes)

This is certainly the simplest and most obvious solution. It might get messy in practice though. What if I climb the bottom half of one route and link it into the top half of another? Then I would need to add another route, even though all its pitches already exist elsewhere in the database.

We could instead allow for ticks which mix and match pitches from different routes. Then variations can be listed without duplicating pitches and the user can still tick what they actually climbed.

musoke avatar Jun 05 '23 15:06 musoke

I guess in that case one multi-pitch route could have several sub-routes, with individual difficulty, bolts, description. Sounds neat. Yet, I have to admit, viewed from my central European climbing perspective, ticking mixed pitches would be a niche feature (the vast majority of climbs are bolted here).

If you don't disagree with points (1) and (2), I'd try implementing them, as I/we need bolt count for a sensible representation in Central Europe and (3) should not be affected by a String array implementation as proposed (1)+(2).

l4u532 avatar Jun 05 '23 16:06 l4u532

Sounds good!

To be clear: when I said "mix" I meant combining pitches from different routes, not mixed protection. I hadn't thought about that.

musoke avatar Jun 05 '23 16:06 musoke

Why would you use string instead of a valid grade that passes openbeta/sandbag validation?

changing "grade" to String array to include difficulty per pitch

vnugent avatar Jun 05 '23 18:06 vnugent

Maybe a misunderstanding (I am aware of Climb.grades). Anyway, I have been thinking about this and have come to a different conclusion altogether - I am trying to recap the problem and options:

  • Problem: For a given multi-pitch climb, we want to represent multiple boltsCount and grades, e. g. grade = ["6a", "6c", "5c", "6c"] and boltsCount = [4, 6, 3, 3] for each pitch
  • Options
    • (1) Add Climb.boltsCount and re-define Climb.grades so that its fields (vscale, french, …) accept Arrays of Strings (instead of single Strings); I guess not a good idea since this would break existing implementations that expect a single String value to be returned
    • (2) Add Climb.boltsCount and introduce a new field called Climb.gradesArray; this would not break existing implementations, but bloat the schema
    • (3) introduce type Pitch (with fields parent, type, number, grade, length, boltsCount, description) as a child to climb, so that climb gets an optional Array Climb.pitches containing each pitch; this has the most flexibility and allows for detailed per-pitch data, plus it wouldn't break any existing implementation (I assume)
      • Addendum to (3): add Climb.boltsCount for single-pitch (sport) climbs
    • (4) Add Climb.boltsCount and allow Climb.ancestor to also point to a climb (instead of an area); not sure what problems this would introduce, could be a quick solution
  • I am favoring (3), as it would allow storing consistent, non-messy and detailed multi-pitch for all disciplines (ice, trad, alpine, … - even mixing them) and allow ticking off individual pitches, without - presumably - breaking things

What are your thoughts?

l4u532 avatar Jun 11 '23 09:06 l4u532

How about adding a parentClimb to the Climb type allowing to define climbs that preceed the current climb. Additionally add the new Pitch type to mark climbs that can only be climbed by also climbing their parent (they are pitches >= 2nd pitch).

We could go further and (later) add a Multi-Pitch type that groups together climbs in a route with a name and "default" tick bucket. This would allow for multiple combinations of multi-pitch routes and leaves flexibility for ticking.

The user could simply tick a "pre-grouped" Multi-Pitch route and then derive and select other pitches if the climb forked in the middle.

Silthus avatar Jun 11 '23 18:06 Silthus

I would say parentClimb could be misunderstood as top-level parent, I’d rather call it prequelClimb or similar. But I see some downsides with that solution: mixing Climb and Pitch would create redundant data fields. Also, not having a top-level Climb wouldnt allow for displaying max/avg grade, total length, no of pitches and max anchors (ie. how many quick draws should I pack, how long will this take, am I strong enough).

What you state as type Multi-Pitch is what I mean with Climb and Climb.Pitches. If Climb.Pitches > 1, it’s automatically a multi pitch route. I admit that’s not the perfect solution for variations, but I still see that as a Prio B feature (how often do we intentionally fork a multi pitch climb?).


From: Silthus @.> Sent: Sunday, June 11, 2023 8:16:39 PM To: OpenBeta/openbeta-graphql @.> Cc: Klaus @.>; Comment @.> Subject: Re: [OpenBeta/openbeta-graphql] multipitch routes (Issue #266)

How about adding a parentClimb to the Climb type allowing to define climbs that preceed the current climb. Additionally add the new Pitch type to mark climbs that can only be climbed by also climbing their parent (they are pitches >= 2nd pitch).

We could go further and (later) add a Multi-Pitch type that groups together climbs in a route with a name and "default" tick bucket. This would allow for multiple combinations of multi-pitch routes and leaves flexibility for ticking.

The user could simply tick a "pre-grouped" Multi-Pitch route and then derive and select other pitches if the climb forked in the middle.

— Reply to this email directly, view it on GitHubhttps://github.com/OpenBeta/openbeta-graphql/issues/266#issuecomment-1586272789, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AVBZ6LWBLPMXF7K7SK237TTXKYDQPANCNFSM6AAAAAAXDU25E4. You are receiving this because you commented.Message ID: @.***>

l4u532 avatar Jun 11 '23 18:06 l4u532

I think 3 is a sensible solution. To help us visualize the document schema, @l4u532 can you share a JSON sample of a climb with nested pitch array?

vnugent avatar Jun 12 '23 16:06 vnugent

Sure. To recap, a Climb would be extended with pitches of Type Pitch:

type Climb {  
  id: ID!  
  uuid: ID!  
  name: String!  
  ...
  pitches: [Pitch]!  # add this line to include Pitch in the Climb type
}

The Type Pitch would contain:

type Pitch {
  uuid: ID!
  parent: ID!
  number: Int!
  grades: GradeType!
  type: ClimbType!
  length: Int!
  boltsCount: Int
  description: String
}

Simplified example JSON for a trad route:

  "data": {
    "climb": {
      "id": "1",
      "uuid": "123e4567-e89b-12d3-a456-426614174000",
      "name": "The Grand Wall",
      "fa": "John Doe",
      "length": 300,
      "grades": {
        "yds": "5.11"
        ...
      },
      "gradeContext": "USA",
      "type": {
        "trad": true,
        "sport": false
        ...
      },
      "safety": "R",
      "pitches": [
        {
          "uuid": "123e4567-e89b-12d3-a456-426614174001",
          "parent": "123e4567-e89b-12d3-a456-426614174000",
          "number": 1,
          "grades": {
            "yds": "5.10"
            ...
          },
          "type": {
            "trad": true,
            "sport": false
            ...
          },
          "length": 50,
          "boltsCount": 0,
          "description": "A beautiful finger crack leading to a bolted face."
        },
        {
          "uuid": "123e4567-e89b-12d3-a456-426614174002",
          "parent": "123e4567-e89b-12d3-a456-426614174000",
          "number": 2,
          "grades": {
            "yds": "5.11"
            ...
          },
          "type": {
            "trad": true,
            "sport": false
            ...
          },
          "length": 70,
          "boltsCount": 0,
          "description": "A challenging pitch with a tricky roof sequence."
        },
        ...
      ],
      ...
    }
  }
}

And an example for an alpine multi-pitch route typical for Central Europe with bolts.

{
  "data": {
    "climb": {
      "id": "2",
      "uuid": "223e4567-e89b-12d3-a456-426614174000",
      "name": "Alpine Dream",
      "fa": "Jane Doe",
      "length": 350,
      "grades": {
        "french": "7a"
        ...
      },
      "gradeContext": "AUS",
      "type": {
        "alpine": true,
        "sport": false
        ...
      },
      "safety": "R",
      "pitches": [
        {
          "uuid": "223e4567-e89b-12d3-a456-426614174001",
          "parent": "223e4567-e89b-12d3-a456-426614174000",
          "number": 1,
          "grades": {
            "french": "6a",
            ...
          },
          "type": {
            "alpine": true,
            "sport": false,
            ...
          },
          "length": 120,
          "boltsCount": 15,
          "description": "Starts with a steep face climb, bolts are well spaced."
        },
        {
          "uuid": "223e4567-e89b-12d3-a456-426614174002",
          "parent": "223e4567-e89b-12d3-a456-426614174000",
          "number": 2,
          "grades": {
            "french": "6b",
            ...
          },
          "type": {
            "alpine": true,
            "sport": false,
            ...
          },
          "length": 110,
          "boltsCount": 20,
          "description": "A bit more challenging, requires careful route finding."
        },
        {
          "uuid": "223e4567-e89b-12d3-a456-426614174003",
          "parent": "223e4567-e89b-12d3-a456-426614174000",
          "number": 3,
          "grades": {
            "french": "7a",
            ...
          },
          "type": {
            "alpine": true,
            "sport": false,
            ...
          },
          "length": 200,
          "boltsCount": 18,
          "description": "A long pitch with a challenging overhang near the end."
        }
      ],
      ...
    }
  }
}

l4u532 avatar Jun 16 '23 07:06 l4u532

Will the climbs still have a description, or is that moved to the pitches?

On Fri, Jun 16, 2023, 03:34 Klaus @.***> wrote:

Sure. To recap, a Climb would be extended with pitches of Type Pitch:

type Climb { id: ID! uuid: ID! name: String! ... pitches: [Pitch]! # add this line to include Pitch in the Climb type }

The Type Pitch would contain:

type Pitch { uuid: ID! parent: ID! number: Int! grades: GradeType! type: ClimbType! length: Int! boltsCount: Int description: String }

Simplified example JSON for a trad route:

"data": { "climb": { "id": "1", "uuid": "123e4567-e89b-12d3-a456-426614174000", "name": "The Grand Wall", "fa": "John Doe", "length": 300, "grades": { "yds": "5.11" ... }, "gradeContext": "USA", "type": { "trad": true, "sport": false ... }, "safety": "R", "pitches": [ { "uuid": "123e4567-e89b-12d3-a456-426614174001", "parent": "123e4567-e89b-12d3-a456-426614174000", "number": 1, "grades": { "yds": "5.10" ... }, "type": { "trad": true, "sport": false ... }, "length": 50, "boltsCount": 0, "description": "A beautiful finger crack leading to a bolted face." }, { "uuid": "123e4567-e89b-12d3-a456-426614174002", "parent": "123e4567-e89b-12d3-a456-426614174000", "number": 2, "grades": { "yds": "5.11" ... }, "type": { "trad": true, "sport": false ... }, "length": 70, "boltsCount": 0, "description": "A challenging pitch with a tricky roof sequence." }, ... ], ... } } }

And an example for an alpine multi-pitch route typical for Central Europe with bolts.

{ "data": { "climb": { "id": "2", "uuid": "223e4567-e89b-12d3-a456-426614174000", "name": "Alpine Dream", "fa": "Jane Doe", "length": 350, "grades": { "french": "7a" ... }, "gradeContext": "AUS", "type": { "alpine": true, "sport": false ... }, "safety": "R", "pitches": [ { "uuid": "223e4567-e89b-12d3-a456-426614174001", "parent": "223e4567-e89b-12d3-a456-426614174000", "number": 1, "grades": { "french": "6a", ... }, "type": { "alpine": true, "sport": false, ... }, "length": 120, "boltsCount": 15, "description": "Starts with a steep face climb, bolts are well spaced." }, { "uuid": "223e4567-e89b-12d3-a456-426614174002", "parent": "223e4567-e89b-12d3-a456-426614174000", "number": 2, "grades": { "french": "6b", ... }, "type": { "alpine": true, "sport": false, ... }, "length": 110, "boltsCount": 20, "description": "A bit more challenging, requires careful route finding." }, { "uuid": "223e4567-e89b-12d3-a456-426614174003", "parent": "223e4567-e89b-12d3-a456-426614174000", "number": 3, "grades": { "french": "7a", ... }, "type": { "alpine": true, "sport": false, ... }, "length": 200, "boltsCount": 18, "description": "A long pitch with a challenging overhang near the end." } ], ... } } }

— Reply to this email directly, view it on GitHub https://github.com/OpenBeta/openbeta-graphql/issues/266#issuecomment-1594247479, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD7ET7E5L6XDPTH2BA6UCD3XLQEB5ANCNFSM6AAAAAAXDU25E4 . You are receiving this because you authored the thread.Message ID: @.***>

musoke avatar Jun 16 '23 09:06 musoke

Will the climbs still have a description, or is that moved to the pitches?

Mostly data for multi pitch routes will lack a per-pitch description, so I see those as optional. Consequently, Climbs will still have a description.

l4u532 avatar Jun 16 '23 10:06 l4u532

Ok, that makes sense

On Fri, Jun 16, 2023, 06:02 Klaus @.***> wrote:

Will the climbs still have a description, or is that moved to the pitches? … <#m_-8696962295770630765_>

Mostly data for multi pitch routes will lack a per-pitch description, so I see those as optional. Consequently, Climbs will still have a description.

— Reply to this email directly, view it on GitHub https://github.com/OpenBeta/openbeta-graphql/issues/266#issuecomment-1594435927, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD7ET7HOHZCVWLUDV5GEZFTXLQVMZANCNFSM6AAAAAAXDU25E4 . You are receiving this because you authored the thread.Message ID: @.***>

musoke avatar Jun 16 '23 10:06 musoke

Will route grades be set manually or inferred from the constituent pitches?

Will a single pitch climb have pitches? If yes, to what extent is that hidden from the user?

On Fri, Jun 16, 2023, 06:04 Nathan Musoke @.***> wrote:

Ok, that makes sense

On Fri, Jun 16, 2023, 06:02 Klaus @.***> wrote:

Will the climbs still have a description, or is that moved to the pitches? … <#m_-9221684871457352779_m_-7938619715882245834_m_-8696962295770630765_>

Mostly data for multi pitch routes will lack a per-pitch description, so I see those as optional. Consequently, Climbs will still have a description.

— Reply to this email directly, view it on GitHub https://github.com/OpenBeta/openbeta-graphql/issues/266#issuecomment-1594435927, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD7ET7HOHZCVWLUDV5GEZFTXLQVMZANCNFSM6AAAAAAXDU25E4 . You are receiving this because you authored the thread.Message ID: @.***>

musoke avatar Jun 16 '23 10:06 musoke

Climb.grade could take the max difficulty of its pitches’ grades. Guidebooks on alpine routes often speak of “obligatory difficulty” one needs to pass. Most often, that's the max grade. In essence, you don’t want to get stuck in a multi-pitch route.

pitches should be an optional array and be left empty for single-pitch routes (aka sport climbing). Only if pitches.length > 1*, a climb would be a multi-pitch climb.

(*could be enforced , e.g.

      if (args.input.pitches.length <= 1) {
        throw new Error('A multi-pitch climb must have more than one pitch.');
      }

)

It is not hidden from the user, but made explicit via (a) making the pitches array optional and (b) the schema description. For the UI part - which I am not considering here - the pitches.length > 1 could inform about a multi-pitch route. (Also, a boolean flag might be introduced based on this logic).

Your thoughts?

l4u532 avatar Jun 16 '23 11:06 l4u532

Climb.grade could take the max difficulty of its pitches’ grades. Guidebooks on alpine routes often speak of “obligatory difficulty” one needs to pass. Most often, that's the max grade. In essence, you don’t want to get stuck in a multi-pitch route.

That was my initial thought too. Are there any cases or communities that do this differently? The only one that comes to mind for me is climbs where the different pitches have different disciplines - then the grades aren't ordered. For example a free pitch (5.10) and an aid pitch (5.8 A1) should combine to give an aid route with grade 5.10 A1. https://github.com/OpenBeta/sandbag/issues/80 will address that. Generalizing slightly will address routes where pitches are a mix of sport/trad/ice/etc.

I think it should be possible to set a grade that overrides the one calculated from the pitches. This allow a route grade to be recorded even when grades of individual pitches are not known.

single-pitch routes (aka sport climbing)

Do we have different definitions here? I've done multi-pitch sport routes and single pitch non-sport routes.

pitches should be an optional array and be left empty for single-pitch routes (aka sport climbing). Only if pitches.length > 1*, a climb would be a multi-pitch climb.

I would tend to allow single pitch climbs to have a single pitch.

musoke avatar Jun 17 '23 13:06 musoke

@l4u532 I like this approach as it gently introduces multi-pitch data structure as an opt-in. We can even define a convention:

  • For single pitch climbs (regardless of styles, trad/sport/ice, etc.) we can use the existing data structure; The pitches array will be empty.
  • For multipitch climbs we will need to identify them and incrementally introduce pitch data to the pitch array.

vnugent avatar Jun 17 '23 15:06 vnugent

Climb.grade could take the max difficulty of its pitches’ grades. Guidebooks on alpine routes often speak of “obligatory difficulty” one needs to pass. Most often, that's the max grade. In essence, you don’t want to get stuck in a multi-pitch route.

That was my initial thought too. Are there any cases or communities that do this differently?

I know the use case that one may skip a difficult pitch by using an alternative route (neighbouring route, a tree, or other part of the rock not defined as part of the route), which would lower the "obligatory grade". Yet I suggest we start by taking the max of the grades and leave such cases for the description fields.

The only one that comes to mind for me is climbs where the different pitches have different disciplines - then the grades aren't ordered. For example a free pitch (5.10) and an aid pitch (5.8 A1) should combine to give an aid route with grade 5.10 A1. OpenBeta/sandbag#80 will address that. Generalizing slightly will address routes where pitches are a mix of sport/trad/ice/etc.

Good point and similar problem as above. I would consider support for independent free/aid grades separate from this issue, though. (btw. climbs that alternative between ice and rock are called Mixed Climbing and difficulties are denominated from MI1-MI14 (see https://en.wikipedia.org/wiki/Mixed_climbing#Grading), so imho no problem of mixing grades there).

I think it should be possible to set a grade that overrides the one calculated from the pitches. This allow a route grade to be recorded even when grades of individual pitches are not known.

Agreed. Auto-calculate the max grade of pitches, but Climb.grades overrides it, if set.

single-pitch routes (aka sport climbing)

Do we have different definitions here? I've done multi-pitch sport routes and single pitch non-sport routes.

Very possible. I haven't found a clear-cut definition. Such definitions also tend to differ between geographies. Let's agree that multi-pitches may be sport-climbing, too. The important differentiator is pitches.length > 1.

pitches should be an optional array and be left empty for single-pitch routes (aka sport climbing). Only if pitches.length > 1*, a climb would be a multi-pitch climb.

I would tend to allow single pitch climbs to have a single pitch.

Also totally fine with me. Again, the important differentiator is pitches.length > 1.

l4u532 avatar Jun 19 '23 12:06 l4u532

I would consider support for independent free/aid grades separate from this issue, though. (btw. climbs that alternative between ice and rock are called Mixed Climbing and difficulties are denominated from MI1-MI14 (see https://en.wikipedia.org/wiki/Mixed_climbing#Grading), so imho no problem of mixing grades there).

I'm thinking of something slightly different: one climb, where the pitches don't all have the same type of climbing.

In anycase, I think what you're proposing will cover this case since you have a "type" field for each pitch.

musoke avatar Jun 20 '23 00:06 musoke