Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,35 @@ And then you are good to go

## Usage

Please see this [test file](bidirectional_ch_test.go#L17)

I hope it's pretty clear, but here is little explanation:
```go
g := Graph{} // Prepare variable for storing graph
graphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programm
g.PrepareContracts() // Compute contraction hierarchies
u := 144031 // Define source vertex
v := 452090 // Define target vertex
ans, path := g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
```

* Shortest path

Please see this [test file](bidirectional_ch_test.go#L17)

I hope it's pretty clear, but here is little explanation:
```go
g := Graph{} // Prepare variable for storing graph
graphFromCSV(&g, "data/pgrouting_osm.csv") // Import CSV-file file into programm
g.PrepareContracts() // Compute contraction hierarchies
u := 144031 // Define source vertex
v := 452090 // Define target vertex
ans, path := g.ShortestPath(u, v) // Get shortest path and it's cost between source and target vertex
```

* Isochrones

Please see this [test file](isochrones_test.go#L7)
```go
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 search
if err != nil {
t.Error(err)
return
}
```

### If you want to import OSM (Open Street Map) file then follow instructions for [osm2ch](https://github.com/LdDl/osm2ch#osm2ch)

## Benchmark
Expand Down
50 changes: 50 additions & 0 deletions isochrones.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch

import (
"container/heap"
"fmt"
)

// Isochrones Returns set of vertices and corresponding distances restricted by maximum travel cost for source vertex
// source - source vertex (user defined label)
// maxCost - restriction on travel cost for breadth search
// See ref. https://wiki.openstreetmap.org/wiki/Isochrone and https://en.wikipedia.org/wiki/Isochrone_map
// Note: implemented breadth-first searching path algorithm does not guarantee shortest pathes to reachable vertices (until all edges have cost 1.0). See ref: https://en.wikipedia.org/wiki/Breadth-first_search
// Note: result for estimated costs could be also inconsistent due nature of data structure
func (graph *Graph) Isochrones(source int64, maxCost float64) (map[int64]float64, error) {
ok := true
if source, ok = graph.mapping[source]; !ok {
return nil, fmt.Errorf("No such source")
}
Q := &minheapSTD{}
heap.Init(Q)
distance := make(map[int64]float64, len(graph.Vertices))
Q.Push(minheapNode{id: source, distance: 0})
visit := make(map[int64]bool)
for Q.Len() != 0 {
next := heap.Pop(Q).(minheapNode)
visit[next.id] = true
if next.distance <= maxCost {
distance[graph.Vertices[next.id].Label] = next.distance
vertexList := graph.Vertices[next.id].outEdges
costList := graph.Vertices[next.id].outECost
for i := range vertexList {
neighbor := vertexList[i]
if v1, ok1 := graph.contracts[next.id]; ok1 {
if _, ok2 := v1[neighbor]; ok2 {
// Ignore contract
continue
}
}
target := vertexList[i]
cost := costList[i]
alt := distance[graph.Vertices[next.id].Label] + cost
if visit[target] {
continue
}
Q.Push(minheapNode{id: target, distance: alt})
}
}
}
return distance, nil
}
82 changes: 82 additions & 0 deletions isochrones_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ch

import (
"testing"
)

func TestIsochrones(t *testing.T) {
correctIsochrones := map[int64]float64{
5: 0.0,
3: 1.0,
4: 1.0,
6: 1.0,
7: 3.0,
1: 3.0,
8: 4.0, // <---- Because of breadth-first search
9: 2.0,
}
graph := Graph{}

vertices := []V{
V{from: 5, to: 3, weight: 1.0},
V{from: 5, to: 4, weight: 1.0},
V{from: 5, to: 6, weight: 1.0},
V{from: 5, to: 7, weight: 2.0},
V{from: 3, to: 7, weight: 2.0},
V{from: 6, to: 9, weight: 1.0},
V{from: 7, to: 8, weight: 4.0},
V{from: 7, to: 3, weight: 2.0},
V{from: 9, to: 8, weight: 2.0},
V{from: 8, to: 10, weight: 3.0},
V{from: 3, to: 1, weight: 2.0},
V{from: 1, to: 2, weight: 3.0},
V{from: 4, to: 11, weight: 7.0},
V{from: 11, to: 2, weight: 2.0},
V{from: 2, to: 11, weight: 2.0},
}

for i := range vertices {
err := graph.CreateVertex(vertices[i].from)
if err != nil {
t.Error(err)
return
}
err = graph.CreateVertex(vertices[i].to)
if err != nil {
t.Error(err)
return
}
err = graph.AddEdge(vertices[i].from, vertices[i].to, vertices[i].weight)
if err != nil {
t.Error(err)
return
}
}

graph.PrepareContracts() // This is excess in current example, but just for proof that contraction map isn't used.

sourceVertex := int64(5)
maxCost := 5.0
isochrones, err := graph.Isochrones(sourceVertex, maxCost)
if err != nil {
t.Error(err)
return
}

if len(isochrones) != len(correctIsochrones) {
t.Errorf("Number of isochrones should be %d, but got %d", len(correctIsochrones), len(isochrones))
return
}

for k, val := range isochrones {
correctValue, ok := correctIsochrones[k]
if !ok {
t.Errorf("Isochrones should contain vertex %d, but it does not", k)
return
}
if val != correctValue {
t.Errorf("Travel cost to vertex %d should be %f, but got %f", k, correctValue, val)
return
}
}
}