-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopsVisitation.go
More file actions
121 lines (108 loc) · 4.28 KB
/
opsVisitation.go
File metadata and controls
121 lines (108 loc) · 4.28 KB
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
package wildlifenl
import (
"context"
"math"
"net/http"
"time"
"github.com/UtrechtUniversity/wildlifenl/models"
"github.com/UtrechtUniversity/wildlifenl/stores"
"github.com/danielgtaylor/huma/v2"
"github.com/paulmach/orb"
"github.com/paulmach/orb/planar"
"github.com/paulmach/orb/project"
)
type VisitationHolder struct {
Body *models.Visitation `json:"visitation"`
}
type VisitationInput struct {
Start time.Time `query:"start" format:"date-time" required:"true" doc:"The start moment of the time span."`
End time.Time `query:"end" format:"date-time" required:"true" doc:"The end moment of the time span."`
LivingLabID string `query:"livingLabID" format:"uuid" required:"true" doc:"The ID of the living lab."`
CellSize float64 `query:"cellSize" minimum:"20" maximum:"10000" required:"true" doc:"The size of the heatmap cells in meters."`
}
type visitationOperations Operations
func newVisitationOperations() *visitationOperations {
return &visitationOperations{Endpoint: "visitation"}
}
func (o *visitationOperations) RegisterGetForLivingLab(api huma.API) {
name := "Get Visitation For Living Lab"
description := "Retrieve visitation within a time span for a certain living lab."
path := "/" + o.Endpoint + "/"
scopes := []string{"nature-area-manager", "wildlife-manager", "herd-manager"}
method := http.MethodGet
huma.Register(api, huma.Operation{
OperationID: name, Summary: name, Path: path, Method: method, Tags: []string{o.Endpoint}, Description: generateDescription(description, scopes), Security: []map[string][]string{{"auth": scopes}},
}, func(ctx context.Context, input *VisitationInput) (*VisitationHolder, error) {
if !input.Start.Before(input.End) {
return nil, huma.Error400BadRequest("'start' must be before 'end'")
}
livingLab, err := stores.NewLivingLabStore(relationalDB).Get(input.LivingLabID)
if err != nil {
return nil, handleError(err)
}
livingLabPoints := make([]orb.Point, 0)
for _, p := range livingLab.Definition {
livingLabPoints = append(livingLabPoints, orb.Point{p.Longitude, p.Latitude})
}
livingLabPoints = append(livingLabPoints, orb.Point{livingLab.Definition[0].Longitude, livingLab.Definition[0].Latitude})
trackingReadings, err := stores.NewTrackingReadingStore(relationalDB, timeseriesDB).GetForTimespan(input.Start, input.End)
if err != nil {
return nil, handleError(err)
}
humanPoints := make([]orb.Point, 0)
for _, r := range trackingReadings {
humanPoints = append(humanPoints, orb.Point{r.Location.Longitude, r.Location.Latitude})
}
mercatorPolygon := project.Polygon(orb.Polygon{livingLabPoints}, project.WGS84.ToMercator)
b := mercatorPolygon.Bound()
minX := b.Min[0]
minY := b.Min[1]
maxX := b.Max[0]
maxY := b.Max[1]
nx := int(math.Ceil((maxX - minX) / input.CellSize))
ny := int(math.Ceil((maxY - minY) / input.CellSize))
type cell struct {
centroidX float64
centroidY float64
count int
}
cells := make(map[int]cell)
key := func(x, y int) int { return x*1000000 + y }
for ix := range nx {
centroidX := minX + (float64(ix)+0.5)*input.CellSize
for iy := range ny {
centroidY := minY + (float64(iy)+0.5)*input.CellSize
if planar.RingContains(mercatorPolygon[0], orb.Point{centroidX, centroidY}) { // Keep cells whose centroid is inside the polygon.
cells[key(ix, iy)] = cell{centroidX: centroidX, centroidY: centroidY, count: 0}
}
}
}
for _, p := range humanPoints { // Aggregate human points into cells.
mercatorPoint := project.Point(p, project.WGS84.ToMercator)
x := mercatorPoint[0]
y := mercatorPoint[1]
ix := int(math.Floor((x - minX) / input.CellSize))
iy := int(math.Floor((y - minY) / input.CellSize))
if ix < 0 || iy < 0 || ix >= nx || iy >= ny {
continue
}
k := key(ix, iy)
if gc, ok := cells[k]; ok {
gc.count++
cells[k] = gc
}
}
visitation := models.Visitation{
LivingLab: livingLab,
Cells: make([]models.VisitationCell, 0),
}
for _, c := range cells {
centroidWGS := project.Point(orb.Point{c.centroidX, c.centroidY}, project.Mercator.ToWGS84)
visitation.Cells = append(visitation.Cells, models.VisitationCell{
Centroid: models.Point{Latitude: centroidWGS[1], Longitude: centroidWGS[0]},
Count: c.count,
})
}
return &VisitationHolder{Body: &visitation}, nil
})
}