diff --git a/endpoint.go b/endpoint.go
index 63246f54..6520ffb3 100644
--- a/endpoint.go
+++ b/endpoint.go
@@ -36,6 +36,21 @@ func getArcgisOauthCallback(w http.ResponseWriter, r *http.Request) {
}
http.Redirect(w, r, BaseURL+"/", http.StatusFound)
}
+
+func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) {
+ cell_str := chi.URLParam(r, "cell")
+ if cell_str == "" {
+ respondError(w, "There should always be a cell", nil, http.StatusBadRequest)
+ return
+ }
+ cell, err := HexToInt64(cell_str)
+ if err != nil {
+ respondError(w, "Cannot convert provided cell to uint64", err, http.StatusBadRequest)
+ return
+ }
+ htmlCell(r.Context(), w, user, cell)
+}
+
func getFavicon(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "image/x-icon")
diff --git a/h3.go b/h3.go
index 1162f726..ea189b8c 100644
--- a/h3.go
+++ b/h3.go
@@ -9,27 +9,20 @@ import (
"github.com/uber/h3-go/v4"
)
-func h3Indexes() []h3.Cell {
- //[] uint64{0x852a134ffffffff})
- /*result := make([]h3.H3Index, 0)
- for _, v := range values {
- result = append(result, v)
- }
- return result*/
- return []h3.Cell{
- 0x8629aab2fffffff,
- 0x8629a8627ffffff,
- 0x8629a8607ffffff,
- 0x8629a8717ffffff,
- 0x8629a8617ffffff,
- 0x8629a8407ffffff,
- 0x8629a871fffffff,
- 0x8629a845fffffff,
- 0x8629aab27ffffff,
- 0x8629a84e7ffffff,
+/*
+func h3ToBoundsGeoJSON(c h3.Cell) (string, error) {
+ b, err := h3.CellToBoundary(c)
+ if err != nil {
+ respondError(w, "Failed to get cell boundary", err, http.StatusInternalServerError)
+ return
}
+ features, err := geojson2h3.ToFeatureCollection(b)
+ if err != nil {
+ return "", fmt.Errorf("Failed to convert boundary to
}
-func h3ToGeoJSON(indexes []h3.Cell) (string, error) {
+*/
+
+func h3ToGeoJSON(indexes []h3.Cell) (interface{}, error) {
featureCollection, err := geojson2h3.ToFeatureCollection(indexes)
if err != nil {
return "", fmt.Errorf("Failed to get feature collection: %w", err)
@@ -37,14 +30,6 @@ func h3ToGeoJSON(indexes []h3.Cell) (string, error) {
return featureCollection.JSON(), nil
}
-func sampleGeoJSON() (string, error) {
- indexes := h3Indexes()
- featureCollection, err := geojson2h3.ToFeatureCollection(indexes)
- if err != nil {
- return "", fmt.Errorf("Failed to get feature collection: %w", err)
- }
- return featureCollection.JSON(), nil
-}
func main2() {
resolution := 9
object, err := geojson.Parse(`{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"shape":"Polygon","name":"Unnamed Layer","category":"default"},"geometry":{"type":"Polygon","coordinates":[[[-73.901303,40.756892],[-73.893924,40.743755],[-73.871476,40.756278],[-73.863378,40.764175],[-73.871444,40.768467],[-73.879852,40.760014],[-73.885515,40.764045],[-73.891522,40.761054],[-73.901303,40.756892]]]},"id":"a6ca1b7e-9ddf-4425-ad07-8a895f7d6ccf"}]}`, nil)
diff --git a/html.go b/html.go
index a8ec9389..135a7915 100644
--- a/html.go
+++ b/html.go
@@ -19,6 +19,7 @@ import (
"github.com/aarondl/opt/null"
//"github.com/riverqueue/river/rivershared/util/slogutil"
"github.com/stephenafamo/bob/dialect/psql/sm"
+ "github.com/uber/h3-go/v4"
)
//go:embed templates/*
@@ -26,6 +27,7 @@ var embeddedFiles embed.FS
// Authenticated pages
var (
+ cell = newBuiltTemplate("cell", "authenticated")
dashboard = newBuiltTemplate("dashboard", "authenticated")
oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated")
settings = newBuiltTemplate("settings", "authenticated")
@@ -52,7 +54,7 @@ var (
signin = newBuiltTemplate("signin", "base")
signup = newBuiltTemplate("signup", "base")
)
-var components = [...]string{"header"}
+var components = [...]string{"header", "map"}
type BuiltTemplate struct {
files []string
@@ -60,13 +62,20 @@ type BuiltTemplate struct {
template *template.Template
}
-type Link struct {
- Href string
- Title string
+type ComponentMap struct {
+ Center LatLng
+ GeoJSON interface{}
+ MapboxToken string
+ Zoom int
}
type ContentAuthenticatedPlaceholder struct {
User User
}
+type ContentCell struct {
+ CellBoundary h3.CellBoundary
+ MapData ComponentMap
+ User User
+}
type ContentPhoneCall struct {
DistrictName string
}
@@ -82,8 +91,8 @@ type ContentDashboard struct {
CountMosquitoSources int
CountServiceRequests int
Geo template.JS
- MapboxToken string
LastSync *time.Time
+ MapData ComponentMap
Org string
RecentRequests []ServiceRequestSummary
User User
@@ -94,6 +103,14 @@ type ContentSignin struct {
InvalidCredentials bool
}
type ContentSignup struct{}
+type LatLng struct {
+ Lat float64
+ Lng float64
+}
+type Link struct {
+ Href string
+ Title string
+}
type ServiceRequestSummary struct {
Date time.Time
Location string
@@ -165,12 +182,45 @@ func extractInitials(name string) string {
return initials.String()
}
-func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
- geo, err := sampleGeoJSON()
+func htmlCell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64) {
+ userContent, err := contentForUser(ctx, user)
if err != nil {
- respondError(w, "Failed to get geo", err, http.StatusInternalServerError)
+ respondError(w, "Failed to get user", err, http.StatusInternalServerError)
return
}
+ center, err := h3.Cell(c).LatLng()
+ if err != nil {
+ respondError(w, "Failed to get center", err, http.StatusInternalServerError)
+ return
+ }
+ boundary, err := h3.Cell(c).Boundary()
+ if err != nil {
+ respondError(w, "Failed to get boundary", err, http.StatusInternalServerError)
+ return
+ }
+ geojson, err := h3ToGeoJSON([]h3.Cell{h3.Cell(c)})
+ if err != nil {
+ respondError(w, "Failed to get boundaries", err, http.StatusInternalServerError)
+ return
+ }
+ resolution := h3.Cell(c).Resolution()
+ data := ContentCell{
+ CellBoundary: boundary,
+ MapData: ComponentMap{
+ Center: LatLng{
+ Lat: center.Lat,
+ Lng: center.Lng,
+ },
+ GeoJSON: geojson,
+ MapboxToken: MapboxToken,
+ Zoom: resolution + 5,
+ },
+ User: userContent,
+ }
+ renderOrError(w, cell, &data)
+}
+
+func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
org, err := user.Organization().One(ctx, PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
@@ -220,12 +270,13 @@ func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User
CountInspections: int(inspectionCount),
CountMosquitoSources: int(sourceCount),
CountServiceRequests: int(serviceCount),
- Geo: template.JS(geo),
LastSync: lastSync,
- MapboxToken: MapboxToken,
- Org: org.Name.MustGet(),
- RecentRequests: requests,
- User: userContent,
+ MapData: ComponentMap{
+ MapboxToken: MapboxToken,
+ },
+ Org: org.Name.MustGet(),
+ RecentRequests: requests,
+ User: userContent,
}
renderOrError(w, dashboard, data)
}
@@ -369,11 +420,30 @@ func htmlSignup(w http.ResponseWriter, path string) {
renderOrError(w, signup, data)
}
+func latLngDisplay(ll h3.LatLng) string {
+ latDir := "N"
+ latVal := ll.Lat
+ if ll.Lat < 0 {
+ latDir = "S"
+ latVal = -ll.Lat
+ }
+
+ lngDir := "E"
+ lngVal := ll.Lng
+ if ll.Lng < 0 {
+ lngDir = "W"
+ lngVal = -ll.Lng
+ }
+
+ return fmt.Sprintf("%.4f° %s, %.4f° %s", latVal, latDir, lngVal, lngDir)
+}
+
func makeFuncMap() template.FuncMap {
funcMap := template.FuncMap{
- "bigNumber": bigNumber,
- "timeElapsed": timeElapsed,
- "timeSince": timeSince,
+ "bigNumber": bigNumber,
+ "latLngDisplay": latLngDisplay,
+ "timeElapsed": timeElapsed,
+ "timeSince": timeSince,
}
return funcMap
}
diff --git a/main.go b/main.go
index bead3bb1..f4de23c3 100644
--- a/main.go
+++ b/main.go
@@ -111,6 +111,7 @@ func main() {
r.Post("/signup", postSignup)
// Authenticated endpoints
+ r.Method("GET", "/cell/{cell}", NewEnsureAuth(getCellDetails))
r.Method("GET", "/settings", NewEnsureAuth(getSettings))
r.Method("GET", "/vector-tiles/{org_id}/{tileset_id}/{zoom}/{x}/{y}.{format}", NewEnsureAuth(getVectorTiles))
diff --git a/strings.go b/strings.go
new file mode 100644
index 00000000..077dd6b5
--- /dev/null
+++ b/strings.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "strconv"
+ "strings"
+)
+
+func HexToInt64(hexStr string) (int64, error) {
+ // Remove "0x" prefix if present
+ hexStr = strings.TrimPrefix(hexStr, "0x")
+ hexStr = strings.TrimPrefix(hexStr, "0X")
+
+ // Parse hex string to uint64
+ value, err := strconv.ParseInt(hexStr, 16, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ return value, nil
+}
+
+func HexToUint64(hexStr string) (uint64, error) {
+ // Remove "0x" prefix if present
+ hexStr = strings.TrimPrefix(hexStr, "0x")
+ hexStr = strings.TrimPrefix(hexStr, "0X")
+
+ // Parse hex string to uint64
+ value, err := strconv.ParseUint(hexStr, 16, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ return value, nil
+}
diff --git a/templates/cell.html b/templates/cell.html
new file mode 100644
index 00000000..9e5a34f5
--- /dev/null
+++ b/templates/cell.html
@@ -0,0 +1,245 @@
+{{template "authenticated.html" .}}
+
+{{define "title"}}Dash{{end}}
+{{define "extraheader"}}
+{{template "map" .MapData}}
+
+{{end}}
+{{define "content"}}
+
+
+
+
+
Location Data View
+
+
+
+
+
+
+
+
+
+
Approximate Address:
+
123 Main St, Anytown, ST 12345
+
+
+
+
Cell Coordinates (Hexagon):
+
+
+
+ {{ range $i, $cb := .CellBoundary }}
+
+ | Vertex {{$i}}: |
+ {{$cb|latLngDisplay}} |
+
+ {{end}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Address |
+ Source Type |
+ Last Inspected |
+ Last Treated |
+
+
+
+
+ | 123 Main St |
+ Standing Water |
+ 04/15/2023 |
+ 04/16/2023 |
+
+
+ | 125 Main St |
+ Catch Basin |
+ 04/15/2023 |
+ 04/16/2023 |
+
+
+ | 130 Main St |
+ Drainage Ditch |
+ 04/14/2023 |
+ 04/15/2023 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Approximate Address |
+ Inspection Date |
+ Technician Comments |
+
+
+
+
+ | 123 Main St |
+ 04/15/2023 |
+ Found larvae in standing water near gutter downspout. |
+
+
+ | 125 Main St |
+ 04/15/2023 |
+ Catch basin had moderate larvae activity. |
+
+
+ | 130 Main St |
+ 04/14/2023 |
+ Drainage ditch showing signs of mosquito breeding. |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Approximate Address |
+ Treatment Date |
+ Insecticide Used |
+ Technician Notes |
+
+
+
+
+ | 123 Main St |
+ 04/16/2023 |
+ Bacillus thuringiensis israelensis (Bti) |
+ Applied larvicide to standing water. |
+
+
+ | 125 Main St |
+ 04/16/2023 |
+ Methoprene |
+ Treated catch basin with long-lasting formula. |
+
+
+ | 130 Main St |
+ 04/15/2023 |
+ Bacillus sphaericus |
+ Applied to drainage ditch, full coverage achieved. |
+
+
+ | 135 Main St |
+ 04/14/2023 |
+ Bacillus thuringiensis israelensis (Bti) |
+ Applied to small pond area. |
+
+
+ | 140 Main St |
+ 04/14/2023 |
+ Methoprene |
+ Applied to standing water in yard. |
+
+
+ | 145 Main St |
+ 04/13/2023 |
+ Bacillus sphaericus |
+ Treated problem area behind property. |
+
+
+
+
+
+
+
+
+
+
+
+{{end}}
diff --git a/templates/components/map.html b/templates/components/map.html
new file mode 100644
index 00000000..99fbb239
--- /dev/null
+++ b/templates/components/map.html
@@ -0,0 +1,67 @@
+{{define "map"}}
+
+
+
+
+{{end}}
diff --git a/templates/dashboard.html b/templates/dashboard.html
index 58778d1e..c218682f 100644
--- a/templates/dashboard.html
+++ b/templates/dashboard.html
@@ -5,10 +5,9 @@