-
Notifications
You must be signed in to change notification settings - Fork 6
/
db.go
163 lines (129 loc) · 3.85 KB
/
db.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package locodedb
import (
"errors"
"fmt"
"github.com/nspcc-dev/locode-db/pkg/locodedb"
)
// SourceTable is an interface of the UN/LOCODE table.
type SourceTable interface {
// Must iterate over all entries of the table
// and pass next entry to the handler.
//
// Must return handler's errors directly.
IterateAll(func(Record) error) error
}
// AirportRecord represents the entry in airport database.
type AirportRecord struct {
// Name of the country where airport is located.
CountryName string
// Geo point where airport is located.
Point locodedb.Point
}
// ErrAirportNotFound is returned by AirportRecord readers
// when the required airport is not found.
var ErrAirportNotFound = errors.New("airport not found")
// AirportDB is an interface of airport database.
type AirportDB interface {
// Must return the record by UN/LOCODE table record.
//
// Must return ErrAirportNotFound if there is no
// related airport in the database.
Get(Record) (*AirportRecord, error)
}
// ContinentsDB is an interface of continent database.
type ContinentsDB interface {
// Must return continent of the geo point.
PointContinent(locodedb.Point) (*locodedb.Continent, error)
}
var ErrSubDivNotFound = errors.New("subdivision not found")
var ErrCountryNotFound = errors.New("country not found")
// NamesDB is an interface of the location namespace.
type NamesDB interface {
// Must resolve a country code to a country name.
//
// Must return ErrCountryNotFound if there is no
// country with the provided code.
CountryName(*locodedb.CountryCode) (string, error)
// Must resolve (country code, subdivision code) to
// a subdivision name.
//
// Must return ErrSubDivNotFound if either country or
// subdivision is not presented in database.
SubDivName(*locodedb.CountryCode, string) (string, error)
}
// FillDatabase generates the location database based on the UN/LOCODE table.
func FillDatabase(table SourceTable, airports AirportDB, continents ContinentsDB, names NamesDB, db CsvDB) error {
var newData []Data
if err := table.IterateAll(func(tableRecord Record) error {
if tableRecord.LOCODE[1] == "" {
return nil
}
dbKey, err := locodedb.NewKey(tableRecord.LOCODE[0], tableRecord.LOCODE[1])
if err != nil {
return err
}
crd, err := CoordinatesFromString(tableRecord.Coordinates)
if err != nil {
if errors.Is(err, locodedb.ErrInvalidString) {
return nil
}
return err
}
geoPoint, err := PointFromCoordinates(crd)
if err != nil {
return fmt.Errorf("could not parse geo point: %w", err)
}
countryName := ""
if geoPoint == (locodedb.Point{}) {
airportRecord, err := airports.Get(tableRecord)
if err != nil {
if errors.Is(err, ErrAirportNotFound) {
return nil
}
return err
}
geoPoint = airportRecord.Point
countryName = airportRecord.CountryName
}
dbRecord := locodedb.Record{
Location: tableRecord.NameWoDiacritics,
SubDivCode: tableRecord.SubDiv,
Point: geoPoint,
}
if countryName == "" {
countryName, err = names.CountryName(dbKey.CountryCode())
if err != nil {
if errors.Is(err, ErrCountryNotFound) {
return nil
}
return err
}
}
dbRecord.Country = countryName
if subDivCode := dbRecord.SubDivCode; subDivCode != "" {
subDivName, err := names.SubDivName(dbKey.CountryCode(), subDivCode)
if err != nil {
if errors.Is(err, ErrSubDivNotFound) {
return nil
}
return err
}
dbRecord.SubDivName = subDivName
}
continent, err := continents.PointContinent(geoPoint)
if err != nil {
return fmt.Errorf("could not calculate continent geo point: %w", err)
} else if *continent == locodedb.ContinentUnknown {
return nil
}
dbRecord.Cont = *continent
newData = append(newData, Data{*dbKey, dbRecord})
return nil
}); err != nil {
return err
}
if err := db.Put(newData); err != nil {
return err
}
return nil
}