comparison pkg/imports/sr.go @ 959:6ab012d0f0c2

Started with writing an importer job for sounding results.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 16 Oct 2018 18:20:50 +0200
parents
children e23ae2c83427
comparison
equal deleted inserted replaced
958:2818ad6c7d32 959:6ab012d0f0c2
1 package imports
2
3 import (
4 "archive/zip"
5 "bufio"
6 "context"
7 "database/sql"
8 "encoding/json"
9 "errors"
10 "fmt"
11 "io"
12 "log"
13 "os"
14 "path/filepath"
15 "strconv"
16 "strings"
17 "time"
18
19 "gemma.intevation.de/gemma/pkg/octree"
20 )
21
22 type SoundingResult struct {
23 who string
24 dir string
25 }
26
27 const SoundingResultDateFormat = "2006-01-02"
28
29 type SoundingResultDate struct{ time.Time }
30
31 type SoundingResultMeta struct {
32 Date SoundingResultDate `json:"date"`
33 Bottleneck string `json:"bottleneck"`
34 EPSG uint `json:"epsg"`
35 DepthReference string `json:"depth-reference"`
36 }
37
38 func (srd *SoundingResultDate) UnmarshalJSON(data []byte) error {
39 var s string
40 if err := json.Unmarshal(data, &s); err != nil {
41 return err
42 }
43 d, err := time.Parse(SoundingResultDateFormat, s)
44 if err == nil {
45 *srd = SoundingResultDate{d}
46 }
47 return err
48 }
49
50 func (sr *SoundingResult) Who() string {
51 return sr.who
52 }
53
54 func (sr *SoundingResult) CleanUp() error {
55 return os.RemoveAll(sr.dir)
56 }
57
58 func find(needle string, haystack []*zip.File) *zip.File {
59 needle = strings.ToLower(needle)
60 for _, straw := range haystack {
61 if strings.HasSuffix(strings.ToLower(straw.Name), needle) {
62 return straw
63 }
64 }
65 return nil
66 }
67
68 func loadMeta(f *zip.File) (*SoundingResultMeta, error) {
69 r, err := f.Open()
70 if err != nil {
71 return nil, err
72 }
73 defer r.Close()
74 var m SoundingResultMeta
75 err = json.NewDecoder(r).Decode(&m)
76 return &m, err
77 }
78
79 func (m *SoundingResultMeta) validate(conn *sql.Conn) error {
80
81 var b bool
82 err := conn.QueryRowContext(context.Background(),
83 `SELECT true FROM internal.depth_references WHERE depth_reference = $1`,
84 m.DepthReference).Scan(&b)
85 switch {
86 case err == sql.ErrNoRows:
87 return fmt.Errorf("Unknown depth reference '%s'\n", m.DepthReference)
88 case err != nil:
89 return err
90 case !b:
91 return errors.New("Unexpected depth reference")
92 }
93
94 err = conn.QueryRowContext(context.Background(),
95 `SELECT true FROM waterway.bottlenecks WHERE bottleneck_id = $1`,
96 m.Bottleneck).Scan(&b)
97 switch {
98 case err == sql.ErrNoRows:
99 return fmt.Errorf("Unknown bottleneck '%s'\n", m.Bottleneck)
100 case err != nil:
101 return err
102 case !b:
103 return errors.New("Unexpected bottleneck")
104 }
105
106 return nil
107 }
108
109 func loadXYZReader(r io.Reader) (octree.MultiPointZ, error) {
110 mpz := make(octree.MultiPointZ, 0, 250000)
111 s := bufio.NewScanner(r)
112
113 for line := 1; s.Scan(); line++ {
114 text := s.Text()
115 var p octree.Vertex
116 // fmt.Sscanf(text, "%f,%f,%f") is 4 times slower.
117 idx := strings.IndexByte(text, ',')
118 if idx == -1 {
119 log.Printf("format error in line %d\n", line)
120 continue
121 }
122 var err error
123 if p.X, err = strconv.ParseFloat(text[:idx], 64); err != nil {
124 log.Printf("format error in line %d: %v\n", line, err)
125 continue
126 }
127 text = text[idx+1:]
128 if idx = strings.IndexByte(text, ','); idx == -1 {
129 log.Printf("format error in line %d\n", line)
130 continue
131 }
132 if p.Y, err = strconv.ParseFloat(text[:idx], 64); err != nil {
133 log.Printf("format error in line %d: %v\n", line, err)
134 continue
135 }
136 text = text[idx+1:]
137 if p.Z, err = strconv.ParseFloat(text, 64); err != nil {
138 log.Printf("format error in line %d: %v\n", line, err)
139 continue
140 }
141 mpz = append(mpz, p)
142 }
143
144 if err := s.Err(); err != nil {
145 return nil, err
146 }
147
148 return mpz, nil
149 }
150
151 func loadXYZ(f *zip.File) (octree.MultiPointZ, error) {
152 r, err := f.Open()
153 if err != nil {
154 return nil, err
155 }
156 defer r.Close()
157 return loadXYZReader(r)
158 }
159
160 func (sr *SoundingResult) Do(conn *sql.Conn) error {
161
162 z, err := zip.OpenReader(filepath.Join(sr.dir, "upload.zip"))
163 if err != nil {
164 return err
165 }
166 defer z.Close()
167
168 mf := find("meta.json", z.File)
169 if mf == nil {
170 return errors.New("Cannot find 'meta.json'")
171 }
172
173 m, err := loadMeta(mf)
174 if err != nil {
175 return err
176 }
177
178 if err := m.validate(conn); err != nil {
179 return err
180 }
181
182 xyzf := find(".xyz", z.File)
183 if xyzf == nil {
184 return errors.New("Cannot find any *.xyz file")
185 }
186
187 xyz, err := loadXYZ(xyzf)
188 if err != nil {
189 return err
190 }
191
192 if len(xyz) == 0 {
193 return errors.New("XYZ does not contain any vertices.")
194 }
195
196 // TODO: Implement more.
197
198 return nil
199 }