/
photo.go
97 lines (78 loc) · 2.33 KB
/
photo.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
package model
import (
"fmt"
"io"
"regexp"
"strconv"
"time"
"github.com/rwcarlsen/goexif/exif"
"github.com/pkg/errors"
)
type Photo struct {
ID PhotoID `json:"id"`
Year int `json:"year"`
Month int `json:"month"`
Day int `json:"day"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Timestamp time.Time `json:"timestamp"`
S3ObjectKey string `json:"s3_object_key"`
UserID UserID `json:"user_id"`
}
type (
PhotoID string
)
const (
MINIMUM_LAT = -90
MAXIMUM_LAT = 90
MINIMUM_LON = -180
MAXIMUM_LON = 180
S3_KEY_PREFIX = "touring-log/photo"
)
// example key: touring-log/photo/thing=thingName/year=2022/month=01/day=12/1656422254000.gz.
var keyValidator = regexp.MustCompile(`^touring-log/photo/thing=.+/year=\d{4}/month=[01][0-9]/day=[0-2][0-9]/\d{13}.jpeg.gz$`)
func NewPhoto(id PhotoID, time time.Time, lat, lon float64, user_id UserID, unit string) (*Photo, error) {
key := fmt.Sprintf("%s/thing=%s/year=%d/month=%02d/day=%02d/%s.jpeg.gz", S3_KEY_PREFIX, unit, time.Year(), time.Month(), time.Day(), strconv.Itoa(int(time.UnixMilli())))
data := &Photo{
ID: id,
Year: time.Year(),
Month: int(time.Month()),
Day: time.Day(),
Lat: lat,
Lon: lon,
Timestamp: time,
S3ObjectKey: key,
UserID: user_id,
}
if err := photoSpecSatisfied(data); err != nil {
return nil, err
}
return data, nil
}
func photoSpecSatisfied(data *Photo) error {
if !keyValidator.MatchString(data.S3ObjectKey) {
return errors.Errorf("failed to validate key: %+v", data.S3ObjectKey)
}
if data.Lat < MINIMUM_LAT || data.Lat > MAXIMUM_LAT {
return errors.Errorf("failed to satisfy GPS Lat Spec: %+v", data.Lat)
}
if data.Lon < MINIMUM_LON || data.Lon > MAXIMUM_LON {
return errors.Errorf("failed to satisfy GPS Lon Spec: %+v", data.Lon)
}
return nil
}
func ExtractMetadata(r io.Reader) (lat, lon float64, time time.Time, err error) {
x, err := exif.Decode(r)
if err != nil {
return 0, 0, time, errors.Wrapf(err, "failed to decode exif")
}
lat, lon, err = x.LatLong()
if err != nil {
return 0, 0, time, errors.Wrapf(err, "failed to extract lat-lon")
}
time, err = x.DateTime()
if err != nil {
return 0, 0, time, errors.Wrapf(err, "failed to extract time")
}
return lat, lon, time, nil
}