-
Notifications
You must be signed in to change notification settings - Fork 2
/
stops.go
134 lines (121 loc) · 3.22 KB
/
stops.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package bete
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
)
type BusStop struct {
ID string
Description string
RoadName string
Location Location
}
// NearbyBusStop represents how far away a bus stop is.
type NearbyBusStop struct {
BusStop
Distance float32
}
// Location represents a latitude and longitude coordinate.
type Location struct {
Latitude float32
Longitude float32
}
func (l *Location) Scan(src interface{}) error {
point := src.(string)
n, err := fmt.Sscanf(point, "(%f,%f)", &l.Longitude, &l.Latitude)
if err != nil {
return errors.Wrap(err, "error scanning point")
}
if n != 2 {
return errors.New("not enough values")
}
return nil
}
type BusStopRepository interface {
Find(id string) (BusStop, error)
// Nearby returns up to limit bus stops within radius km of the point specified by lat and lon.
Nearby(lat, lon, radius float32, limit int) ([]NearbyBusStop, error)
// Search searches for stops with a text query.
Search(query string, limit int) ([]BusStop, error)
}
type SQLBusStopRepository struct {
DB Conn
}
func (r SQLBusStopRepository) Find(id string) (BusStop, error) {
var stop BusStop
err := r.DB.QueryRow("select id, description, road from stops where id = $1", id).Scan(&stop.ID, &stop.Description, &stop.RoadName)
if err == sql.ErrNoRows {
return stop, ErrNotFound
} else if err != nil {
return stop, errors.Wrap(err, "error querying bus stop")
}
return stop, nil
}
func (r SQLBusStopRepository) Nearby(lat, lon, radius float32, limit int) ([]NearbyBusStop, error) {
location := fmt.Sprintf("(%f, %f)", lon, lat)
rows, err := r.DB.Query(
`select id, road, description, location::text, (location <@> $1) * 1.609344 as distance
from stops
where (location <@> $1) * 1.609344 < $2
order by distance
limit $3`,
location,
radius,
limit,
)
if err != nil {
return nil, errors.Wrap(err, "error querying nearby bus stops")
}
defer rows.Close()
var nearby []NearbyBusStop
for rows.Next() {
var n NearbyBusStop
if err := rows.Scan(&n.ID, &n.RoadName, &n.Description, &n.Location, &n.Distance); err != nil {
return nearby, errors.Wrap(err, "error scanning row")
}
nearby = append(nearby, n)
}
if err := rows.Err(); err != nil {
return nearby, errors.Wrap(err, "error iterating rows")
}
return nearby, nil
}
func (r SQLBusStopRepository) Search(query string, limit int) ([]BusStop, error) {
var rows *sql.Rows
var err error
if query != "" {
rows, err = r.DB.Query(
`select id, road, description, location::text
from stops
where tokens @@ plainto_tsquery($1)
order by ts_rank(tokens, plainto_tsquery($1)) desc
limit $2;`,
query,
limit,
)
} else {
rows, err = r.DB.Query(
`select id, road, description, location::text
from stops
order by id
limit $1;`,
limit,
)
}
if err != nil {
return nil, errors.Wrapf(err, "error searching bus stops (query: %q)", query)
}
defer rows.Close()
var matches []BusStop
for rows.Next() {
var s BusStop
if err := rows.Scan(&s.ID, &s.RoadName, &s.Description, &s.Location); err != nil {
return matches, errors.Wrap(err, "error scanning row")
}
matches = append(matches, s)
}
if err := rows.Err(); err != nil {
return matches, errors.Wrap(err, "error iterating rows")
}
return matches, nil
}