diff --git a/database.go b/database.go index 88e98448..649ffb69 100644 --- a/database.go +++ b/database.go @@ -177,11 +177,16 @@ func updateSummaryTables(ctx context.Context, org *models.Organization) { cellToCount[cell] = cellToCount[cell] + 1 } var to_insert []bob.Mod[*dialect.InsertQuery] = make([]bob.Mod[*dialect.InsertQuery], 0) - to_insert = append(to_insert, im.Into("h3_aggregation", "cell", "resolution", "count_", "type_", "organization_id")) + to_insert = append(to_insert, im.Into("h3_aggregation", "cell", "resolution", "count_", "type_", "organization_id", "geometry")) for cell, count := range cellToCount { - to_insert = append(to_insert, im.Values(psql.Arg(cell.String(), i, count, enums.H3aggregationtypeServicerequest, org.ID))) + polygon, err := cellToPostgisGeometry(cell) + if err != nil { + log.Error().Err(err).Msg("Failed to get PostGIS geometry") + continue + } + // log.Info().Str("polygon", polygon).Msg("Going to insert") + to_insert = append(to_insert, im.Values(psql.Arg(cell.String(), i, count, enums.H3aggregationtypeServicerequest, org.ID), psql.F("st_geomfromtext", psql.S(polygon), 4326))) } - //to_insert = append(to_insert, im.OnConflict("h3_aggregation_cell_organization_id_type__key").DoUpdate( to_insert = append(to_insert, im.OnConflict("cell, organization_id, type_").DoUpdate( im.SetCol("count_").To(psql.Raw("EXCLUDED.count_")), )) diff --git a/h3.go b/h3.go index 4f96be13..1162f726 100644 --- a/h3.go +++ b/h3.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "strings" "github.com/Gleipnir-Technology/go-geojson2h3" "github.com/tidwall/geojson" @@ -35,6 +36,15 @@ 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) @@ -79,3 +89,23 @@ func getCell(x, y float64, resolution int) (h3.Cell, error) { latLng := h3.NewLatLng(y, x) return h3.LatLngToCell(latLng, resolution) } + +func cellToPostgisGeometry(c h3.Cell) (string, error) { + boundary, err := h3.CellToBoundary(c) + if err != nil { + return "", fmt.Errorf("Failed to get cell boundary: %w", err) + } + var sb strings.Builder + + for i, p := range boundary { + if i > 0 { + sb.WriteString(",") + } + fmt.Fprintf(&sb, "%g %g", p.Lng, p.Lat) + } + // add the first point on to the end to close the polygon + sb.WriteString(",") + fmt.Fprintf(&sb, "%g %g", boundary[0].Lng, boundary[0].Lat) + + return fmt.Sprintf("POLYGON((%s))", sb.String()), nil +} diff --git a/html.go b/html.go index ec51aa40..a8ec9389 100644 --- a/html.go +++ b/html.go @@ -166,7 +166,7 @@ func extractInitials(name string) string { } func htmlDashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { - geo, err := h3ToGeoJSON(h3Indexes()) + geo, err := sampleGeoJSON() if err != nil { respondError(w, "Failed to get geo", err, http.StatusInternalServerError) return diff --git a/main.go b/main.go index cf9b9e35..0cb8a623 100644 --- a/main.go +++ b/main.go @@ -111,6 +111,7 @@ func main() { // Authenticated endpoints r.Method("GET", "/settings", NewEnsureAuth(getSettings)) + r.Method("GET", "/vector-tiles/{org_id}/{tileset_id}/{zoom}/{x}/{y}.{format}", NewEnsureAuth(getVectorTiles)) localFS := http.Dir("./static") FileServer(r, "/static", localFS, embeddedStaticFS, "static") diff --git a/migrations/00014_h3_aggregation.sql b/migrations/00014_h3_aggregation.sql index 3e197c97..fe429e70 100644 --- a/migrations/00014_h3_aggregation.sql +++ b/migrations/00014_h3_aggregation.sql @@ -8,10 +8,11 @@ CREATE TYPE H3AggregationType AS ENUM ( CREATE TABLE h3_aggregation ( id SERIAL PRIMARY KEY, cell h3index NOT NULL, - resolution INT NOT NULL, count_ INTEGER NOT NULL, - type_ H3AggregationType NOT NULL, + geometry public.geometry(Polygon,4326), organization_id INTEGER REFERENCES organization (id) NOT NULL, + resolution INT NOT NULL, + type_ H3AggregationType NOT NULL, UNIQUE(cell, organization_id, type_)); -- +goose Down diff --git a/templates/dashboard.html b/templates/dashboard.html index 1b54125b..a87252d5 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -11,36 +11,70 @@ function onLoad() { mapboxgl.accessToken = {{ .MapboxToken }}; const map = new mapboxgl.Map({ container: 'map', // container ID - style: 'mapbox://styles/mapbox/streets-v12', // style URL - center: [-74.5, 40], // starting position [lng, lat] - zoom: 9, // starting zoom + style: 'mapbox://styles/mapbox/standard', // style URL + center: [-119.3, 36.327], // starting position [lng, lat] + //center: [7.01, 50.74], + zoom: 9 // starting zoom }); map.on("load", function() { console.log("Map post-load..."); - const sourceId = 'h3-hexes'; - const layerId = 'h3-hexes-layer'; - let source = map.getSource(sourceId); + map.addSource('tegola-bonn', { + 'type': 'vector', + 'tiles': [ + //'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333' + 'https://tegola.nidus.cloud/maps/bonn/{z}/{x}/{y}' + ] + //'minzoom': 6, + //'maxzoom': 14 + }); + map.addSource('tegola-nidus', { + 'type': 'vector', + 'tiles': [ + //'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333' + 'https://tegola.nidus.cloud/maps/nidus/{z}/{x}/{y}' + ] + //'minzoom': 6, + //'maxzoom': 14 + }); + map.addLayer({ + 'id': 'bonn', // Layer ID + 'type': 'fill', + 'source': 'tegola-bonn', // ID of the tile source created above + 'source-layer': 'lakes', + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'fill-opacity': 0.1, + 'line-opacity': 0.6, + 'line-color': 'rgb(53, 175, 109)', + 'line-width': 2 + } + //slot: 'middle' // middle slot in Mapbox Standard style + }); + map.addLayer({ + 'id': 'nidus', // Layer ID + 'type': 'fill', + 'source': 'tegola-nidus', // ID of the tile source created above + 'source-layer': 'h3_aggregation', + 'layout': { + 'line-cap': 'round', + 'line-join': 'round' + }, + 'paint': { + 'fill-opacity': 0.3, + 'line-opacity': 0.6, + 'line-color': 'rgb(53, 175, 109)', + 'line-width': 2 + } + //slot: 'middle' // middle slot in Mapbox Standard style + }); - if (!source) { - map.addSource(sourceId, { - type: 'geojson', - data: geojson - }); - map.addLayer({ - id: layerId, - source: sourceId, - type: 'fill', - interactive: false, - paint: { - 'fill-color': '#F00000', - 'fill-opacity': 0.3 - } - }); - source = map.getSource(sourceId); - } - source.setData(geojson); console.log("Map post-load done."); }); + + map.addControl(new mapboxgl.NavigationControl()); console.log("Map init done."); } window.addEventListener("load", onLoad);