| // Copyright ©2014 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package testgraphs |
| |
| import ( |
| "errors" |
| "fmt" |
| "math" |
| |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/graph/iterator" |
| "gonum.org/v1/gonum/graph/simple" |
| ) |
| |
| const ( |
| Closed = '*' // Closed is the closed grid node representation. |
| Open = '.' // Open is the open grid node representation. |
| Unknown = '?' // Unknown is the unknown grid node representation. |
| ) |
| |
| // Grid is a 2D grid planar undirected graph. |
| type Grid struct { |
| // AllowDiagonal specifies whether |
| // diagonally adjacent nodes can |
| // be connected by an edge. |
| AllowDiagonal bool |
| // UnitEdgeWeight specifies whether |
| // finite edge weights are returned as |
| // the unit length. Otherwise edge |
| // weights are the Euclidean distance |
| // between connected nodes. |
| UnitEdgeWeight bool |
| |
| // AllVisible specifies whether |
| // non-open nodes are visible |
| // in calls to Nodes and HasNode. |
| AllVisible bool |
| |
| open []bool |
| r, c int |
| } |
| |
| // NewGrid returns an r by c grid with all positions |
| // set to the specified open state. |
| func NewGrid(r, c int, open bool) *Grid { |
| states := make([]bool, r*c) |
| if open { |
| for i := range states { |
| states[i] = true |
| } |
| } |
| return &Grid{ |
| open: states, |
| r: r, |
| c: c, |
| } |
| } |
| |
| // NewGridFrom returns a grid specified by the rows strings. All rows must |
| // be the same length and must only contain the Open or Closed characters, |
| // NewGridFrom will panic otherwise. |
| func NewGridFrom(rows ...string) *Grid { |
| if len(rows) == 0 { |
| return nil |
| } |
| for i, r := range rows[:len(rows)-1] { |
| if len(r) != len(rows[i+1]) { |
| panic("grid: unequal row lengths") |
| } |
| } |
| states := make([]bool, 0, len(rows)*len(rows[0])) |
| for _, r := range rows { |
| for _, b := range r { |
| switch b { |
| case Closed: |
| states = append(states, false) |
| case Open: |
| states = append(states, true) |
| default: |
| panic(fmt.Sprintf("grid: invalid state: %q", r)) |
| } |
| } |
| } |
| return &Grid{ |
| open: states, |
| r: len(rows), |
| c: len(rows[0]), |
| } |
| } |
| |
| // Nodes returns all the open nodes in the grid if AllVisible is |
| // false, otherwise all nodes are returned. |
| func (g *Grid) Nodes() graph.Nodes { |
| var nodes []graph.Node |
| for id, ok := range g.open { |
| if ok || g.AllVisible { |
| nodes = append(nodes, simple.Node(id)) |
| } |
| } |
| return iterator.NewOrderedNodes(nodes) |
| } |
| |
| // Node returns the node with the given ID if it exists in the graph, |
| // and nil otherwise. |
| func (g *Grid) Node(id int64) graph.Node { |
| if g.has(id) { |
| return simple.Node(id) |
| } |
| return nil |
| } |
| |
| // has returns whether id represents a node in the grid. The state of |
| // the AllVisible field determines whether a non-open node is present. |
| func (g *Grid) has(id int64) bool { |
| return 0 <= id && id < int64(len(g.open)) && (g.AllVisible || g.open[id]) |
| } |
| |
| // HasOpen returns whether n is an open node in the grid. |
| func (g *Grid) HasOpen(id int64) bool { |
| return 0 <= id && id < int64(len(g.open)) && g.open[id] |
| } |
| |
| // Set sets the node at position (r, c) to the specified open state. |
| func (g *Grid) Set(r, c int, open bool) { |
| if r < 0 || r >= g.r { |
| panic("grid: illegal row index") |
| } |
| if c < 0 || c >= g.c { |
| panic("grid: illegal column index") |
| } |
| g.open[r*g.c+c] = open |
| } |
| |
| // Dims returns the dimensions of the grid. |
| func (g *Grid) Dims() (r, c int) { |
| return g.r, g.c |
| } |
| |
| // RowCol returns the row and column of the id. RowCol will panic if the |
| // node id is outside the range of the grid. |
| func (g *Grid) RowCol(id int64) (r, c int) { |
| if id < 0 || int64(len(g.open)) <= id { |
| panic("grid: illegal node id") |
| } |
| return int(id) / g.c, int(id) % g.c |
| } |
| |
| // XY returns the cartesian coordinates of n. If n is not a node |
| // in the grid, (NaN, NaN) is returned. |
| func (g *Grid) XY(id int64) (x, y float64) { |
| if !g.has(id) { |
| return math.NaN(), math.NaN() |
| } |
| r, c := g.RowCol(id) |
| return float64(c), float64(r) |
| } |
| |
| // NodeAt returns the node at (r, c). The returned node may be open or closed. |
| func (g *Grid) NodeAt(r, c int) graph.Node { |
| if r < 0 || r >= g.r || c < 0 || c >= g.c { |
| return nil |
| } |
| return simple.Node(r*g.c + c) |
| } |
| |
| // From returns all the nodes reachable from u. Reachabilty requires that both |
| // ends of an edge must be open. |
| func (g *Grid) From(uid int64) graph.Nodes { |
| if !g.HasOpen(uid) { |
| return graph.Empty |
| } |
| nr, nc := g.RowCol(uid) |
| var to []graph.Node |
| for r := nr - 1; r <= nr+1; r++ { |
| for c := nc - 1; c <= nc+1; c++ { |
| if v := g.NodeAt(r, c); v != nil && g.HasEdgeBetween(uid, v.ID()) { |
| to = append(to, v) |
| } |
| } |
| } |
| if len(to) == 0 { |
| return graph.Empty |
| } |
| return iterator.NewOrderedNodes(to) |
| } |
| |
| // HasEdgeBetween returns whether there is an edge between u and v. |
| func (g *Grid) HasEdgeBetween(uid, vid int64) bool { |
| if !g.HasOpen(uid) || !g.HasOpen(vid) || uid == vid { |
| return false |
| } |
| ur, uc := g.RowCol(uid) |
| vr, vc := g.RowCol(vid) |
| if abs(ur-vr) > 1 || abs(uc-vc) > 1 { |
| return false |
| } |
| return g.AllowDiagonal || ur == vr || uc == vc |
| } |
| |
| func abs(i int) int { |
| if i < 0 { |
| return -i |
| } |
| return i |
| } |
| |
| // Edge returns the edge between u and v. |
| func (g *Grid) Edge(uid, vid int64) graph.Edge { |
| return g.WeightedEdgeBetween(uid, vid) |
| } |
| |
| // WeightedEdge returns the weighted edge between u and v. |
| func (g *Grid) WeightedEdge(uid, vid int64) graph.WeightedEdge { |
| return g.WeightedEdgeBetween(uid, vid) |
| } |
| |
| // EdgeBetween returns the edge between u and v. |
| func (g *Grid) EdgeBetween(uid, vid int64) graph.Edge { |
| return g.WeightedEdgeBetween(uid, vid) |
| } |
| |
| // WeightedEdgeBetween returns the weighted edge between u and v. |
| func (g *Grid) WeightedEdgeBetween(uid, vid int64) graph.WeightedEdge { |
| if g.HasEdgeBetween(uid, vid) { |
| if !g.AllowDiagonal || g.UnitEdgeWeight { |
| return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: 1} |
| } |
| ux, uy := g.XY(uid) |
| vx, vy := g.XY(vid) |
| return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: math.Hypot(ux-vx, uy-vy)} |
| } |
| return nil |
| } |
| |
| // Weight returns the weight of the given edge. |
| func (g *Grid) Weight(xid, yid int64) (w float64, ok bool) { |
| if xid == yid { |
| return 0, true |
| } |
| if !g.HasEdgeBetween(xid, yid) { |
| return math.Inf(1), false |
| } |
| if e := g.EdgeBetween(xid, yid); e != nil { |
| if !g.AllowDiagonal || g.UnitEdgeWeight { |
| return 1, true |
| } |
| ux, uy := g.XY(e.From().ID()) |
| vx, vy := g.XY(e.To().ID()) |
| return math.Hypot(ux-vx, uy-vy), true |
| } |
| return math.Inf(1), true |
| } |
| |
| // String returns a string representation of the grid. |
| func (g *Grid) String() string { |
| b, _ := g.Render(nil) |
| return string(b) |
| } |
| |
| // Render returns a text representation of the graph |
| // with the given path included. If the path is not a path |
| // in the grid Render returns a non-nil error and the |
| // path up to that point. |
| func (g *Grid) Render(path []graph.Node) ([]byte, error) { |
| b := make([]byte, g.r*(g.c+1)-1) |
| for r := 0; r < g.r; r++ { |
| for c := 0; c < g.c; c++ { |
| if g.open[r*g.c+c] { |
| b[r*(g.c+1)+c] = Open |
| } else { |
| b[r*(g.c+1)+c] = Closed |
| } |
| } |
| if r < g.r-1 { |
| b[r*(g.c+1)+g.c] = '\n' |
| } |
| } |
| |
| // We don't use topo.IsPathIn at the outset because we |
| // want to draw as much as possible before failing. |
| for i, n := range path { |
| id := n.ID() |
| if !g.has(id) || (i != 0 && !g.HasEdgeBetween(path[i-1].ID(), id)) { |
| if 0 <= id && id < int64(len(g.open)) { |
| r, c := g.RowCol(id) |
| b[r*(g.c+1)+c] = '!' |
| } |
| return b, errors.New("grid: not a path in graph") |
| } |
| r, c := g.RowCol(id) |
| switch i { |
| case len(path) - 1: |
| b[r*(g.c+1)+c] = 'G' |
| case 0: |
| b[r*(g.c+1)+c] = 'S' |
| default: |
| b[r*(g.c+1)+c] = 'o' |
| } |
| } |
| return b, nil |
| } |