Commit
Author: Kevin Schoon [kevinschoon@gmail.com]
Hash: 39f41a56b546ece97be657071e19849ec576ccf3
Timestamp: Sat, 16 Jun 2018 21:46:51 +0000 (6 years ago)

+253 -55 +/-5 browse
new features
1diff --git a/encoding.go b/encoding.go
2new file mode 100644
3index 0000000..bdc2597
4--- /dev/null
5+++ b/encoding.go
6 @@ -0,0 +1,41 @@
7+ package main
8+
9+ import (
10+ "bytes"
11+ "io"
12+
13+ "gonum.org/v1/gonum/graph"
14+ "gonum.org/v1/gonum/graph/encoding/dot"
15+ )
16+
17+ type Encoding string
18+
19+ func (e Encoding) String() string {
20+ return string(e)
21+ }
22+
23+ const (
24+ Json = Encoding("Json")
25+ Dot = Encoding("Dot")
26+ )
27+
28+ type Encoder interface {
29+ Encode(io.Writer) error
30+ }
31+
32+ type dotEncoder struct {
33+ graph graph.Graph
34+ }
35+
36+ func (d dotEncoder) Encode(w io.Writer) error {
37+ raw, err := dot.Marshal(d.graph, "", "", "", false)
38+ if err != nil {
39+ return err
40+ }
41+ _, err = bytes.NewBuffer(raw).WriteTo(w)
42+ return err
43+ }
44+
45+ func NewEncoder(enc Encoding, g graph.Graph) Encoder {
46+ return dotEncoder{graph: g}
47+ }
48 diff --git a/graph.go b/graph.go
49new file mode 100644
50index 0000000..de31843
51--- /dev/null
52+++ b/graph.go
53 @@ -0,0 +1,49 @@
54+ package main
55+
56+ import (
57+ "gonum.org/v1/gonum/floats"
58+ "gonum.org/v1/gonum/graph"
59+ "gonum.org/v1/gonum/graph/path"
60+ "gonum.org/v1/gonum/graph/simple"
61+ )
62+
63+ func SumWeights(route Route, weighters ...Weighter) float64 {
64+ var values []float64
65+ for _, weighter := range weighters {
66+ values = append(values, weighter(route))
67+ }
68+ return floats.Sum(values)
69+ }
70+
71+ // Weighter evaluates the weight of a route
72+ // based on some criteria
73+ type Weighter func(Route) float64
74+
75+ // ByDistance sets the weight of a route based on the
76+ // distance between two airports.
77+ func ByDistance() Weighter {
78+ return func(r Route) float64 {
79+ return GetDistance(r.from, r.to)
80+ }
81+ }
82+
83+ func Load(airports AirportMap, routes []Route, weighters ...Weighter) graph.Graph {
84+ g := simple.NewWeightedDirectedGraph(0.0, 0.0)
85+ for _, airport := range airports {
86+ g.AddNode(airport)
87+ }
88+ for _, route := range routes {
89+ g.SetWeightedEdge(SetWeight(route, SumWeights(route, weighters...)))
90+ }
91+ return g
92+ }
93+
94+ func Find(start, end Airport, g graph.Graph) Itinerary {
95+ shortest := path.DijkstraFrom(start, g)
96+ path, weight := shortest.To(end)
97+ var airports []Airport
98+ for _, p := range path {
99+ airports = append(airports, p.(Airport))
100+ }
101+ return Itinerary{stops: airports, weight: weight}
102+ }
103 diff --git a/main.go b/main.go
104index dedd038..97e9c1b 100644
105--- a/main.go
106+++ b/main.go
107 @@ -1,7 +1,6 @@
108 package main
109
110 import (
111- "encoding/json"
112 "fmt"
113 "os"
114
115 @@ -9,53 +8,64 @@ import (
116 )
117
118 func main() {
119- app := cli.App("flights", "✈️:✈️ ✈️ ✈️ ✈️ ")
120+ app := cli.App("flights", "✈️ ✈️ ✈️ ✈️ ✈️ ")
121+
122 app.Command("airports", "list all possible airports", func(cmd *cli.Cmd) {
123- var (
124- asJson = cmd.BoolOpt("json", false, "use json encoding")
125- )
126 cmd.Action = func() {
127- airports := Ordered(Airports())
128- if *asJson {
129- maybe(json.NewEncoder(os.Stdout).Encode(airports))
130- } else {
131- for _, airport := range airports {
132- fmt.Println(airport)
133- }
134+ airports := Airports()
135+ for _, airport := range airports.Ordered() {
136+ fmt.Println(airport)
137+ }
138+ }
139+ })
140+
141+ app.Command("routes", "list all possible routes", func(cmd *cli.Cmd) {
142+ cmd.Action = func() {
143+ for _, route := range Routes(Airports()) {
144+ fmt.Println(route)
145 }
146 }
147 })
148
149- app.Command("route", "find all possible routes", func(cmd *cli.Cmd) {
150- cmd.Spec = "[OPTIONS] ARRIVAL DESTINATION"
151+ app.Command("route", "find the best possible routes", func(cmd *cli.Cmd) {
152+ cmd.Spec = "[OPTIONS] DEPARTURE ARRIVAL"
153 var (
154- asJson = cmd.BoolOpt("json", false, "use json encoding")
155- threshold = cmd.IntOpt("threshold", 100, "airport distance threshold")
156- arrivalCode = cmd.StringArg("ARRIVAL", "", "starting airport")
157- departureCode = cmd.StringArg("DESTINATION", "", "ending airport")
158+ //threshold = cmd.IntOpt("threshold", 100, "airport distance threshold")
159+ departureCode = cmd.StringArg("DEPARTURE", "", "starting airport")
160+ arrivalCode = cmd.StringArg("ARRIVAL", "", "ending airport")
161 )
162 cmd.Action = func() {
163 airports := Airports()
164- if _, ok := airports[*arrivalCode]; !ok {
165+ if !airports.HasCode(*arrivalCode) {
166 maybe(fmt.Errorf("bad arrival code: %s", *arrivalCode))
167 }
168- if _, ok := airports[*departureCode]; !ok {
169+ if !airports.HasCode(*departureCode) {
170 maybe(fmt.Errorf("bad departure code: %s", *departureCode))
171 }
172- departures, arrivals := FindRoutes(float64(*threshold), airports, airports[*arrivalCode], airports[*departureCode])
173- if *asJson {
174- data := [][]Airport{departures, arrivals}
175- maybe(json.NewEncoder(os.Stdout).Encode(data))
176- } else {
177- fmt.Println("Departures: ")
178- for _, departure := range departures {
179- fmt.Println(departure)
180- }
181- fmt.Println("Arrivals: ")
182- for _, arrival := range arrivals {
183- fmt.Println(arrival)
184+ //shortest, weight := Load(airports.Airport(*departureCode), airports.Airport(*arrivalCode), opts)
185+ fmt.Println(Find(
186+ airports.Airport(*departureCode),
187+ airports.Airport(*arrivalCode),
188+ Load(airports, Routes(airports), ByDistance()),
189+ ))
190+ //fmt.Println(shortest, weight)
191+ //maybe(NewEncoder(Json, Load(airports, Routes(airports))).Encode(os.Stdout))
192+ /*
193+ departures, arrivals := FindRoutes(float64(*threshold), airports, airports[*arrivalCode], airports[*departureCode])
194+ if *asJson {
195+ data := [][]Airport{departures, arrivals}
196+ maybe(json.NewEncoder(os.Stdout).Encode(data))
197+ } else {
198+ fmt.Println("Departures: ")
199+ for _, departure := range departures {
200+ fmt.Println(departure)
201+ }
202+ fmt.Println("Arrivals: ")
203+ for _, arrival := range arrivals {
204+ fmt.Println(arrival)
205+ }
206 }
207- }
208+ */
209 }
210 })
211 maybe(app.Run(os.Args))
212 diff --git a/types.go b/types.go
213index 2ccfa5c..c60b260 100644
214--- a/types.go
215+++ b/types.go
216 @@ -1,9 +1,44 @@
217 package main
218
219 import (
220+ "bytes"
221 "fmt"
222+ "hash/fnv"
223+ "sort"
224+
225+ "gonum.org/v1/gonum/graph"
226 )
227
228+ // Map of route keys e.g. ORDJFK and
229+ // slice of weights to apply
230+ type WeightMap map[string][]float64
231+
232+ type AirportMap map[string]Airport
233+
234+ func (a AirportMap) Airport(id string) Airport {
235+ return a[id]
236+ }
237+
238+ func (a AirportMap) HasCode(code string) bool {
239+ _, ok := a[code]
240+ return ok
241+ }
242+
243+ func (a AirportMap) Ordered() []Airport {
244+ var (
245+ sorted []Airport
246+ codes []string
247+ )
248+ for key, _ := range a {
249+ codes = append(codes, key)
250+ }
251+ sort.Strings(codes)
252+ for _, code := range codes {
253+ sorted = append(sorted, a[code])
254+ }
255+ return sorted
256+ }
257+
258 type Airport struct {
259 // ICAO code
260 Code string
261 @@ -15,5 +50,52 @@ type Airport struct {
262 }
263
264 func (a Airport) String() string {
265- return fmt.Sprintf("%s:%s:%s:%s:%f:%f", a.Code, a.Name, a.City, a.Country, a.Latitude, a.Longitude)
266+ return fmt.Sprintf(
267+ "%s:%s:%s",
268+ a.Code, a.City, a.Country,
269+ )
270+ }
271+
272+ func (a Airport) DOTID() string { return fmt.Sprintf("\"%s\"", a.Code) }
273+
274+ func (a Airport) ID() int64 {
275+ h := fnv.New64a()
276+ h.Write([]byte(a.String()))
277+ return int64(h.Sum64())
278+ }
279+
280+ type Route struct {
281+ to Airport
282+ from Airport
283+ weight float64
284+ }
285+
286+ func (r Route) String() string {
287+ return fmt.Sprintf("%s->%s", r.from, r.to)
288+ }
289+
290+ func (r Route) To() graph.Node { return r.to }
291+ func (r Route) From() graph.Node { return r.from }
292+ func (r Route) Weight() float64 { return r.weight }
293+
294+ func SetWeight(r Route, weight float64) Route {
295+ return Route{to: r.to, from: r.from, weight: weight}
296+ }
297+
298+ type Itinerary struct {
299+ // Ordered list of airports
300+ stops []Airport
301+ weight float64
302+ }
303+
304+ func (i Itinerary) String() string {
305+ buf := bytes.NewBuffer(nil)
306+ buf.WriteString(fmt.Sprintf("%f:", i.weight))
307+ for j, stop := range i.stops {
308+ buf.WriteString(stop.String())
309+ if j+1 != len(i.stops) {
310+ buf.WriteString("-->")
311+ }
312+ }
313+ return buf.String()
314 }
315 diff --git a/util.go b/util.go
316index 59504c8..ba3c08b 100644
317--- a/util.go
318+++ b/util.go
319 @@ -6,7 +6,6 @@ import (
320 "io"
321 "math"
322 "os"
323- "sort"
324 "strconv"
325 "strings"
326 )
327 @@ -68,39 +67,56 @@ func parseAirport(row []string) *Airport {
328 return airport
329 }
330
331- func Airports() map[string]Airport {
332+ //2B,410,AER,2965,KZN,2990,,0,CR2
333+ func parseRoute(airports AirportMap, row []string) *Route {
334+ if len(row) != 9 {
335+ return nil
336+ }
337+ start, end := row[2], row[4]
338+ if airports.HasCode(start) && airports.HasCode(end) {
339+ if start != end {
340+ return &Route{
341+ to: airports.Airport(end),
342+ from: airports.Airport(start),
343+ }
344+ } else {
345+ return nil
346+ }
347+ } else {
348+ return nil
349+ }
350+ }
351+
352+ func Airports() AirportMap {
353 airports := map[string]Airport{}
354- reader := csv.NewReader(strings.NewReader(data))
355+ reader := csv.NewReader(strings.NewReader(airportData))
356 reader.Comma = ':'
357 for {
358 row, err := reader.Read()
359- if err != nil {
360- if err == io.EOF {
361- return airports
362- }
363- panic(err)
364+ if err == io.EOF {
365+ return airports
366 }
367 airport := parseAirport(row)
368 if airport != nil {
369 airports[airport.Code] = *airport
370 }
371 }
372- return airports
373 }
374
375- func Ordered(airports map[string]Airport) []Airport {
376- var (
377- sorted []Airport
378- codes []string
379- )
380- for key, _ := range airports {
381- codes = append(codes, key)
382- }
383- sort.Strings(codes)
384- for _, code := range codes {
385- sorted = append(sorted, airports[code])
386+ func Routes(airports AirportMap) []Route {
387+ var routes []Route
388+ reader := csv.NewReader(strings.NewReader(routeData))
389+ reader.Comma = ','
390+ for {
391+ row, err := reader.Read()
392+ if err == io.EOF {
393+ return routes
394+ }
395+ route := parseRoute(airports, row)
396+ if route != nil {
397+ routes = append(routes, *route)
398+ }
399 }
400- return sorted
401 }
402
403 func maybe(err error) {