Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - LdDl/ch: Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph. · GitHub
Skip to content

Latest commit

History

367 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GoDocBuild StatusSourcegraphGo Report CardGitHub tag

ch - Contraction Hierarchies

Contraction Hierarchies - technique for speed up of computing shortest path in graph.

This library provides Contraction Hierarchies preprocessing graph technique for Dijkstra's algorithm. Classic implementation of Dijkstra's algorithm, maneuver restrictions extension and isochrones estimation are included also.

Table of Contents

About

This package provides implemented next techniques and algorithms:

  • Dijkstra's algorithm
  • Contraction hierarchies
  • Bidirectional extension of Dijkstra's algorithm with contracted nodes
  • Dynamic edge weight updates (lightweight recustomization)

Installation

Go get

gogetgithub.com/LdDl/ch

Go mod

In your project folder execute next command (assuming you have GO111MODULE=on):

gomodinitmod

Then import library into your code:

package main
import"github.com/LdDl/ch"funcmain() {
x:= ch.Graph{}
_=x
}

and build

gobuild

You will see next output:

go: finding github.com/LdDl/ch v1.10.0
go: downloading github.com/LdDl/ch v1.10.0

And then you are good to go

Usage

  • Shortest path (single-threaded)

    Please see this test file

    I hope it's pretty clear, but here is little explanation:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiesu:=144031// Define source vertexv:=452090// Define target vertexans, path:=g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
  • Shortest path (thread-safe for concurrent use)

    Please see this test file

    If you need to execute shortest path queries from multiple goroutines concurrently, use the QueryPool API:

    g:=Graph{} // Prepare variable for storing graphgraphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programmg.PrepareContractionHierarchies() // Compute contraction hierarchiespool:=g.NewQueryPool() // Create a query pool for concurrent access// Now you can safely call from multiple goroutines:ans, path:=pool.ShortestPath(u, v)
    // One-to-many queries are also supported:costs, paths:=pool.ShortestPathOneToMany(source, targets)

    Important: The default Graph.ShortestPath() method is NOT thread-safe. If you call it from multiple goroutines without synchronization, you may get incorrect results. Use QueryPool for concurrent scenarios.

  • Isochrones

    Please see this test file

    g:=Graph{} // Prepare variable for storing graph// ...// Fill graph with data (vertices and edges)// ...isochrones, err:=graph.Isochrones(sourceVertex, maxCost) // Evaluate isochrones via bread-first searchiferr!=nil {
    t.Error(err)
    return
    }
  • Dynamic edge weight updates (Recustomization)

    Please see this test file

    This feature allows you to update edge weights without rebuilding the entire contraction hierarchy. Useful for:

    • Traffic updates (congestion, accidents and other events)
    • Time-dependent routing
    g:=Graph{}
    graphFromCSV(&g, "data/pgrouting_osm.csv")
    g.PrepareContractionHierarchies()
    // Single update with immediate recustomizationerr:=g.UpdateEdgeWeight(fromVertex, toVertex, newWeight, true)
    // Batch updates (more efficient for multiple changes)g.UpdateEdgeWeight(edge1From, edge1To, weight1, false)
    g.UpdateEdgeWeight(edge2From, edge2To, weight2, false)
    g.UpdateEdgeWeight(edge3From, edge3To, weight3, false)
    g.Recustomize() // Apply all changes at once

    When to use single vs batch updates:

    ScenarioMethodWhy
    One edge changedUpdateEdgeWeight(..., true)Simple, immediate
    Multiple edges changedBatch + Recustomize()Faster, single pass
    Real-time traffic feedBatch + periodic Recustomize()Amortize cost

    How it works:

    flowchart TB
    subgraph Preprocessing["Preprocessing (one-time)"]
    P1[Build CH with importance ordering] --> P2[Store contractionOrder array]
    P2 --> P3[Index shortcuts by Via vertex<br/>shortcutsByVia map]
    end
    subgraph Update["UpdateEdgeWeight call"]
    U1[Convert user labels to internal IDs] --> U2[Update edge weight in<br/>outIncidentEdges & inIncidentEdges]
    U2 --> U3{needRecustom?}
    U3 -->|Yes| R1
    U3 -->|No| U4[Return - batch mode]
    end
    subgraph Recustomize["Recustomize call"]
    R1[For each vertex V in contractionOrder] --> R2[Get shortcuts via V<br/>from shortcutsByVia]
    R2 --> R3[For each shortcut A => C via V]
    R3 --> R4["newCost = cost(A => V) + cost(V => C)"]
    R4 --> R5[Update shortcut.Cost]
    R5 --> R6[Update incident edges]
    R6 --> R3
    end
    P3 --> U1
    R6 -.->|next vertex| R1
    
    Loading

    Processing in contraction order ensures that when updating shortcut A => C via V, the edges A => V and V => C (which might themselves be shortcuts) have already been updated.

    Note: This is a lightweight recustomization inspired by Customizable Contraction Hierarchies (Dibbelt, Strasser, Wagner), but uses the existing importance-based ordering instead of nested dissection. It's simpler and requires no external dependencies, while still providing efficient metric updates.

    In future may be added full CCH support with nested dissection ordering (need to investigate METIS or similar libraries for graph partitioning).

If you want to import OSM (Open Street Map) file then follow instructions for osm2ch

Custom import with pre-computed CH

If you have your own import logic (e.g., reading additional data like GeoJSON coordinates alongside the graph), you need to call FinalizeImport() after loading all vertices, edges, and shortcuts:

graph:=ch.NewGraph()
// Your custom import logic:// - CreateVertex() for each vertex// - AddEdge() for each edge// - SetOrderPos() and SetImportance() for each vertex// - AddShortcut() for each shortcutgraph.FinalizeImport() // Required for recustomization support// Now graph is ready for queries and UpdateEdgeWeight/Recustomize

This is required because FinalizeImport() builds internal data structures (contractionOrder, shortcutsByVia) needed for dynamic edge weight updates.

If you use the built-in ImportFromFile() function, this is called automatically.

Benchmark

You can check benchmarks here

Support

If you have troubles or questions please open an issue.

ToDo

Please see ROADMAP.md

Theory

Dijkstra's algorithm

Bidirectional search

Bidirectional Dijkstra's algorithm's stop condition

Contraction hierarchies

Customizable Contraction Hierarchies - Dibbelt, Strasser, Wagner (2014). The recustomization feature in this library is inspired by CCH concepts.

Video Lectures

Thanks

Thanks to this visual explanation Thanks to this Java implementation of mentioned algorithms

Dependencies

Thanks to paulmach for his OSM-parser written in Go.

Paulmach's license is here (it's MIT)

License

You can check it here

About

Contraction Hierarchies (with bidirectional version of Dijkstra's algorithm) technique for computing shortest path in graph.

Topics

Resources

Code of conduct

Stars

55 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages