Skip to content

Allow arbitrary OSM tags in custom model#3284

Open
karussell wants to merge 11 commits intomasterfrom
more_generic_custom_model
Open

Allow arbitrary OSM tags in custom model#3284
karussell wants to merge 11 commits intomasterfrom
more_generic_custom_model

Conversation

@karussell
Copy link
Copy Markdown
Member

@karussell karussell commented Feb 13, 2026

This is kind of a prototype, but already in good shape to allow arbitrary OSM tags in the custom model via:

{
  "priority": [
    { "if": "tag('cycleway') == 'lane'", "multiply_by": "2"  }
  ]
}
image image

Nice if you want to use OSM tags in a custom model but there is no encoded value yet. What about increasing the chance to have a 'lit' on your route? What about preferring sidewalks? Or how to exclude a certain road based on its street? All and a lot more is possible with this :)

However, there might be some downsides compared to encoded values:

  1. The returning value is currently always a String. Also currently only == and != are possible as operator in the custom model
  2. It might slow down non-CH routing (but depending on how many tags, how frequent they are etc). But CH routing should be fine.
  3. (currently?) not directional, i.e. the values with xy:forward/xy:backward are separate and have no meaning. You have to check them against is_forward to make them meaningful.
  4. Values are not normalized, checked or corrected. They are as raw and natural as OSM is.
  5. Space and memory usage is heavier if it is a frequent tag and/or lots of unique and long values, but can be even lower, especially for rare tags like conditional tags

TODOs:

  • syntax fine? Unfortunately OSM keys can contain : and everything, which forces us to use a different syntax (compared to the current one for encoded values) to still keep the parsing simple, i.e. hand most of the parsing over to Janino. The current syntax is still rather short and avoids double escaping and still gives us a robust parsing from Janino as we just have to replace the ' with " and no regex or other stuff. Also == only looks like a reference comparison but is using equals under the hood making it more convenient to write.
  • better integrate these tags with existing data like street_name in KVStorage. Probably rename back to OSM 'name' and let users use this instead of street_name. Also provide backward compatibility in path details for this.
  • allow more methods like contains and startsWith.
  • what about node tags like barrier? (see my older branch where I had a partial solution for this and also motorway_junction)
  • expose them as path details too
  • further performance & memory usage tuning. A minor tuning was already done using the index instead of the String. We could do a similar thing for the value.
  • improve space usage in KVStorage for longer strings
  • custom model editor

karussell and others added 6 commits February 11, 2026 19:02
…erand orders and null comparisons

- Replace CustomWeightingHelper.kvGet(__kv, key, reverse) with edge.getValue(key)
  since EdgeIteratorState.getValue() already handles directionality internally
- Remove __kv variable declaration and kvGet helper method
- Handle tag.get() on either side of == / != (e.g. 'lane' == tag.get('cycleway'))
- Support null comparisons: tag.get('lit') == null / != null
- Reject double quotes in expressions; require single quotes for string literals

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tag.get('cycleway') now compiles to edge.get(this.kv_cycleway_enc) where
kv_cycleway_enc is a KVStorageEncodedValue holding a pre-resolved key index.
This avoids the HashMap lookup in KVStorage.get() on every edge access by
resolving the string key to its internal int index once at init time.

- New KVStorageEncodedValue implements EncodedValue directly (no bits in
  edge flags, supports two directions)
- KVStorage: add int-based get(pointer, keyIndex, reverse), reserveKey()
  for pre-assigning key indices, and getKeyIndex() for resolution
- EdgeIteratorState: add default get(KVStorageEncodedValue) method falling
  back to getValue(name); BaseGraph.EdgeIteratorStateImpl overrides with
  the fast int-based path
- ConditionalExpressionVisitor: tag.get replacement now emits
  edge.get(this.kv_<field>_enc) instead of edge.getValue("key")
- CustomModelParser: generates KVStorageEncodedValue fields and init code;
  adds kvFieldName() to sanitize OSM keys to Java identifiers
- GraphHopper: registers KVStorageEncodedValue per stored tag in
  buildEncodingManager; initKVStorageEncodedValues() reserves keys and
  sets indices in both import and load paths
- config-example.yml: add default stored_tags with common cycling/walking
  tags (cycleway, cycleway:left/right, lit, sidewalk)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the tag.get('xy') syntax with the shorter tag('xy') for
accessing KV-stored OSM tags in custom model expressions.

Add is_forward as a special variable in custom model expressions,
generating `boolean is_forward = !reverse` to expose the edge traversal
direction. This allows directional tag conditions like
`is_forward && tag('cycleway:right') == 'lane'`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant