From b6e1bffd7964fd3d202d60ce599e87c8d42b835f Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 17 Apr 2026 17:47:38 +0000 Subject: [PATCH] Add support for satellite tiles, with caching --- api/tile.go | 2 +- db/dberrors/tile.cached_image.bob.go | 2 +- db/dberrors/tile.service.bob.go | 26 + db/dbinfo/tile.cached_image.bob.go | 48 +- db/dbinfo/tile.service.bob.go | 156 ++++ db/migrations/00142_tile_image_provider.sql | 25 + db/migrations/00143_tile_service_stadia.sql | 7 + db/models/arcgis.service_map.bob.go | 105 +-- db/models/bob_loaders.bob.go | 4 + db/models/bob_where.bob.go | 3 + db/models/tile.cached_image.bob.go | 222 +++--- db/models/tile.service.bob.go | 780 ++++++++++++++++++++ platform/arcgis.go | 7 + platform/tile.go | 148 ++-- platform/user.go | 1 - resource/compliance.go | 8 +- stadia/cmd/tile-raster/main.go | 4 +- stadia/map_tile_raster.go | 11 +- 18 files changed, 1318 insertions(+), 241 deletions(-) create mode 100644 db/dberrors/tile.service.bob.go create mode 100644 db/dbinfo/tile.service.bob.go create mode 100644 db/migrations/00142_tile_image_provider.sql create mode 100644 db/migrations/00143_tile_service_stadia.sql create mode 100644 db/models/tile.service.bob.go diff --git a/api/tile.go b/api/tile.go index 8926e24e..7c05754b 100644 --- a/api/tile.go +++ b/api/tile.go @@ -30,7 +30,7 @@ func getTile(w http.ResponseWriter, r *http.Request, user platform.User) { http.Error(w, "can't parse x as an integer", http.StatusBadRequest) return } - err = platform.GetTile(r.Context(), w, user.Organization, uint(z), uint(y), uint(x)) + err = platform.GetTile(r.Context(), w, user.Organization, true, uint(z), uint(y), uint(x)) if err != nil { log.Error().Err(err).Msg("failed to do tile") http.Error(w, "failed to do tile", http.StatusInternalServerError) diff --git a/db/dberrors/tile.cached_image.bob.go b/db/dberrors/tile.cached_image.bob.go index ce4d642b..9e6294e4 100644 --- a/db/dberrors/tile.cached_image.bob.go +++ b/db/dberrors/tile.cached_image.bob.go @@ -7,7 +7,7 @@ var TileCachedImageErrors = &tileCachedImageErrors{ ErrUniqueCachedImagePkey: &UniqueConstraintError{ schema: "tile", table: "cached_image", - columns: []string{"arcgis_id", "x", "y", "z"}, + columns: []string{"service_id", "x", "y", "z"}, s: "cached_image_pkey", }, } diff --git a/db/dberrors/tile.service.bob.go b/db/dberrors/tile.service.bob.go new file mode 100644 index 00000000..c301ad46 --- /dev/null +++ b/db/dberrors/tile.service.bob.go @@ -0,0 +1,26 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var TileServiceErrors = &tileServiceErrors{ + ErrUniqueServicePkey: &UniqueConstraintError{ + schema: "tile", + table: "service", + columns: []string{"id"}, + s: "service_pkey", + }, + + ErrUniqueServiceNameUnique: &UniqueConstraintError{ + schema: "tile", + table: "service", + columns: []string{"name"}, + s: "service_name_unique", + }, +} + +type tileServiceErrors struct { + ErrUniqueServicePkey *UniqueConstraintError + + ErrUniqueServiceNameUnique *UniqueConstraintError +} diff --git a/db/dbinfo/tile.cached_image.bob.go b/db/dbinfo/tile.cached_image.bob.go index 34a66d76..a2056be7 100644 --- a/db/dbinfo/tile.cached_image.bob.go +++ b/db/dbinfo/tile.cached_image.bob.go @@ -15,15 +15,6 @@ var TileCachedImages = Table[ Schema: "tile", Name: "cached_image", Columns: tileCachedImageColumns{ - ArcgisID: column{ - Name: "arcgis_id", - DBType: "text", - Default: "", - Comment: "", - Nullable: false, - Generated: false, - AutoIncr: false, - }, X: column{ Name: "x", DBType: "integer", @@ -60,6 +51,15 @@ var TileCachedImages = Table[ Generated: false, AutoIncr: false, }, + ServiceID: column{ + Name: "service_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, Indexes: tileCachedImageIndexes{ CachedImagePkey: index{ @@ -67,7 +67,7 @@ var TileCachedImages = Table[ Name: "cached_image_pkey", Columns: []indexColumn{ { - Name: "arcgis_id", + Name: "service_id", Desc: null.FromCond(false, true), IsExpression: false, }, @@ -97,18 +97,18 @@ var TileCachedImages = Table[ }, PrimaryKey: &constraint{ Name: "cached_image_pkey", - Columns: []string{"arcgis_id", "x", "y", "z"}, + Columns: []string{"service_id", "x", "y", "z"}, Comment: "", }, ForeignKeys: tileCachedImageForeignKeys{ - TileCachedImageCachedImageArcgisIDFkey: foreignKey{ + TileCachedImageCachedImageServiceIDFkey: foreignKey{ constraint: constraint{ - Name: "tile.cached_image.cached_image_arcgis_id_fkey", - Columns: []string{"arcgis_id"}, + Name: "tile.cached_image.cached_image_service_id_fkey", + Columns: []string{"service_id"}, Comment: "", }, - ForeignTable: "arcgis.service_map", - ForeignColumns: []string{"arcgis_id"}, + ForeignTable: "tile.service", + ForeignColumns: []string{"id"}, }, }, @@ -116,16 +116,16 @@ var TileCachedImages = Table[ } type tileCachedImageColumns struct { - ArcgisID column - X column - Y column - Z column - IsEmpty column + X column + Y column + Z column + IsEmpty column + ServiceID column } func (c tileCachedImageColumns) AsSlice() []column { return []column{ - c.ArcgisID, c.X, c.Y, c.Z, c.IsEmpty, + c.X, c.Y, c.Z, c.IsEmpty, c.ServiceID, } } @@ -140,12 +140,12 @@ func (i tileCachedImageIndexes) AsSlice() []index { } type tileCachedImageForeignKeys struct { - TileCachedImageCachedImageArcgisIDFkey foreignKey + TileCachedImageCachedImageServiceIDFkey foreignKey } func (f tileCachedImageForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.TileCachedImageCachedImageArcgisIDFkey, + f.TileCachedImageCachedImageServiceIDFkey, } } diff --git a/db/dbinfo/tile.service.bob.go b/db/dbinfo/tile.service.bob.go new file mode 100644 index 00000000..2e3174c1 --- /dev/null +++ b/db/dbinfo/tile.service.bob.go @@ -0,0 +1,156 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var TileServices = Table[ + tileServiceColumns, + tileServiceIndexes, + tileServiceForeignKeys, + tileServiceUniques, + tileServiceChecks, +]{ + Schema: "tile", + Name: "service", + Columns: tileServiceColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('tile.service_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Name: column{ + Name: "name", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ArcgisID: column{ + Name: "arcgis_id", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: tileServiceIndexes{ + ServicePkey: index{ + Type: "btree", + Name: "service_pkey", + Columns: []indexColumn{ + { + Name: "id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + ServiceNameUnique: index{ + Type: "btree", + Name: "service_name_unique", + Columns: []indexColumn{ + { + Name: "name", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "service_pkey", + Columns: []string{"id"}, + Comment: "", + }, + ForeignKeys: tileServiceForeignKeys{ + TileServiceServiceArcgisIDFkey: foreignKey{ + constraint: constraint{ + Name: "tile.service.service_arcgis_id_fkey", + Columns: []string{"arcgis_id"}, + Comment: "", + }, + ForeignTable: "arcgis.service_map", + ForeignColumns: []string{"arcgis_id"}, + }, + }, + Uniques: tileServiceUniques{ + ServiceNameUnique: constraint{ + Name: "service_name_unique", + Columns: []string{"name"}, + Comment: "", + }, + }, + + Comment: "", +} + +type tileServiceColumns struct { + ID column + Name column + ArcgisID column +} + +func (c tileServiceColumns) AsSlice() []column { + return []column{ + c.ID, c.Name, c.ArcgisID, + } +} + +type tileServiceIndexes struct { + ServicePkey index + ServiceNameUnique index +} + +func (i tileServiceIndexes) AsSlice() []index { + return []index{ + i.ServicePkey, i.ServiceNameUnique, + } +} + +type tileServiceForeignKeys struct { + TileServiceServiceArcgisIDFkey foreignKey +} + +func (f tileServiceForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.TileServiceServiceArcgisIDFkey, + } +} + +type tileServiceUniques struct { + ServiceNameUnique constraint +} + +func (u tileServiceUniques) AsSlice() []constraint { + return []constraint{ + u.ServiceNameUnique, + } +} + +type tileServiceChecks struct{} + +func (c tileServiceChecks) AsSlice() []check { + return []check{} +} diff --git a/db/migrations/00142_tile_image_provider.sql b/db/migrations/00142_tile_image_provider.sql new file mode 100644 index 00000000..1b9c9489 --- /dev/null +++ b/db/migrations/00142_tile_image_provider.sql @@ -0,0 +1,25 @@ +-- +goose Up +CREATE TABLE tile.service ( + id SERIAL, + name TEXT NOT NULL, + arcgis_id TEXT REFERENCES arcgis.service_map(arcgis_id), + PRIMARY KEY(id) +); + +INSERT INTO tile.service (name, arcgis_id) + SELECT name, arcgis_id + FROM arcgis.service_map; + +ALTER TABLE tile.cached_image ADD COLUMN service_id INTEGER REFERENCES tile.service(id); + +UPDATE tile.cached_image +SET service_id = tile.service.id +FROM tile.service +WHERE tile.service.arcgis_id = tile.cached_image.arcgis_id; + +ALTER TABLE tile.cached_image + DROP CONSTRAINT cached_image_pkey, + ALTER COLUMN arcgis_id DROP NOT NULL, + ALTER COLUMN service_id SET NOT NULL, + ADD PRIMARY KEY (service_id, x, y, z), + DROP COLUMN arcgis_id; diff --git a/db/migrations/00143_tile_service_stadia.sql b/db/migrations/00143_tile_service_stadia.sql new file mode 100644 index 00000000..ea75d42d --- /dev/null +++ b/db/migrations/00143_tile_service_stadia.sql @@ -0,0 +1,7 @@ +-- +goose Up +INSERT INTO tile.service (name, arcgis_id) VALUES ('stadia', NULL); +ALTER TABLE tile.service + ADD CONSTRAINT service_name_unique UNIQUE (name); +-- +goose Down +ALTER TABLE tile.service DROP CONSTRAINT service_name_unique; +DELETE FROM tile.service WHERE name = 'stadia'; diff --git a/db/models/arcgis.service_map.bob.go b/db/models/arcgis.service_map.bob.go index 5a43bb03..1972ee20 100644 --- a/db/models/arcgis.service_map.bob.go +++ b/db/models/arcgis.service_map.bob.go @@ -44,9 +44,9 @@ type ArcgisServiceMapsQuery = *psql.ViewQuery[*ArcgisServiceMap, ArcgisServiceMa // arcgisServiceMapR is where relationships are stored. type arcgisServiceMapR struct { - Account *ArcgisAccount // arcgis.service_map.service_map_account_id_fkey - ArcgisMapServiceOrganizations OrganizationSlice // organization.organization_arcgis_map_service_id_fkey - ArcgisCachedImages TileCachedImageSlice // tile.cached_image.cached_image_arcgis_id_fkey + Account *ArcgisAccount // arcgis.service_map.service_map_account_id_fkey + ArcgisMapServiceOrganizations OrganizationSlice // organization.organization_arcgis_map_service_id_fkey + ArcgisServices TileServiceSlice // tile.service.service_arcgis_id_fkey } func buildArcgisServiceMapColumns(alias string) arcgisServiceMapColumns { @@ -487,14 +487,14 @@ func (os ArcgisServiceMapSlice) ArcgisMapServiceOrganizations(mods ...bob.Mod[*d )...) } -// ArcgisCachedImages starts a query for related objects on tile.cached_image -func (o *ArcgisServiceMap) ArcgisCachedImages(mods ...bob.Mod[*dialect.SelectQuery]) TileCachedImagesQuery { - return TileCachedImages.Query(append(mods, - sm.Where(TileCachedImages.Columns.ArcgisID.EQ(psql.Arg(o.ArcgisID))), +// ArcgisServices starts a query for related objects on tile.service +func (o *ArcgisServiceMap) ArcgisServices(mods ...bob.Mod[*dialect.SelectQuery]) TileServicesQuery { + return TileServices.Query(append(mods, + sm.Where(TileServices.Columns.ArcgisID.EQ(psql.Arg(o.ArcgisID))), )...) } -func (os ArcgisServiceMapSlice) ArcgisCachedImages(mods ...bob.Mod[*dialect.SelectQuery]) TileCachedImagesQuery { +func (os ArcgisServiceMapSlice) ArcgisServices(mods ...bob.Mod[*dialect.SelectQuery]) TileServicesQuery { pkArcgisID := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -506,8 +506,8 @@ func (os ArcgisServiceMapSlice) ArcgisCachedImages(mods ...bob.Mod[*dialect.Sele psql.F("unnest", psql.Cast(psql.Arg(pkArcgisID), "text[]")), )) - return TileCachedImages.Query(append(mods, - sm.Where(psql.Group(TileCachedImages.Columns.ArcgisID).OP("IN", PKArgExpr)), + return TileServices.Query(append(mods, + sm.Where(psql.Group(TileServices.Columns.ArcgisID).OP("IN", PKArgExpr)), )...) } @@ -627,66 +627,66 @@ func (arcgisServiceMap0 *ArcgisServiceMap) AttachArcgisMapServiceOrganizations(c return nil } -func insertArcgisServiceMapArcgisCachedImages0(ctx context.Context, exec bob.Executor, tileCachedImages1 []*TileCachedImageSetter, arcgisServiceMap0 *ArcgisServiceMap) (TileCachedImageSlice, error) { - for i := range tileCachedImages1 { - tileCachedImages1[i].ArcgisID = omit.From(arcgisServiceMap0.ArcgisID) +func insertArcgisServiceMapArcgisServices0(ctx context.Context, exec bob.Executor, tileServices1 []*TileServiceSetter, arcgisServiceMap0 *ArcgisServiceMap) (TileServiceSlice, error) { + for i := range tileServices1 { + tileServices1[i].ArcgisID = omitnull.From(arcgisServiceMap0.ArcgisID) } - ret, err := TileCachedImages.Insert(bob.ToMods(tileCachedImages1...)).All(ctx, exec) + ret, err := TileServices.Insert(bob.ToMods(tileServices1...)).All(ctx, exec) if err != nil { - return ret, fmt.Errorf("insertArcgisServiceMapArcgisCachedImages0: %w", err) + return ret, fmt.Errorf("insertArcgisServiceMapArcgisServices0: %w", err) } return ret, nil } -func attachArcgisServiceMapArcgisCachedImages0(ctx context.Context, exec bob.Executor, count int, tileCachedImages1 TileCachedImageSlice, arcgisServiceMap0 *ArcgisServiceMap) (TileCachedImageSlice, error) { - setter := &TileCachedImageSetter{ - ArcgisID: omit.From(arcgisServiceMap0.ArcgisID), +func attachArcgisServiceMapArcgisServices0(ctx context.Context, exec bob.Executor, count int, tileServices1 TileServiceSlice, arcgisServiceMap0 *ArcgisServiceMap) (TileServiceSlice, error) { + setter := &TileServiceSetter{ + ArcgisID: omitnull.From(arcgisServiceMap0.ArcgisID), } - err := tileCachedImages1.UpdateAll(ctx, exec, *setter) + err := tileServices1.UpdateAll(ctx, exec, *setter) if err != nil { - return nil, fmt.Errorf("attachArcgisServiceMapArcgisCachedImages0: %w", err) + return nil, fmt.Errorf("attachArcgisServiceMapArcgisServices0: %w", err) } - return tileCachedImages1, nil + return tileServices1, nil } -func (arcgisServiceMap0 *ArcgisServiceMap) InsertArcgisCachedImages(ctx context.Context, exec bob.Executor, related ...*TileCachedImageSetter) error { +func (arcgisServiceMap0 *ArcgisServiceMap) InsertArcgisServices(ctx context.Context, exec bob.Executor, related ...*TileServiceSetter) error { if len(related) == 0 { return nil } var err error - tileCachedImages1, err := insertArcgisServiceMapArcgisCachedImages0(ctx, exec, related, arcgisServiceMap0) + tileServices1, err := insertArcgisServiceMapArcgisServices0(ctx, exec, related, arcgisServiceMap0) if err != nil { return err } - arcgisServiceMap0.R.ArcgisCachedImages = append(arcgisServiceMap0.R.ArcgisCachedImages, tileCachedImages1...) + arcgisServiceMap0.R.ArcgisServices = append(arcgisServiceMap0.R.ArcgisServices, tileServices1...) - for _, rel := range tileCachedImages1 { + for _, rel := range tileServices1 { rel.R.ArcgisServiceMap = arcgisServiceMap0 } return nil } -func (arcgisServiceMap0 *ArcgisServiceMap) AttachArcgisCachedImages(ctx context.Context, exec bob.Executor, related ...*TileCachedImage) error { +func (arcgisServiceMap0 *ArcgisServiceMap) AttachArcgisServices(ctx context.Context, exec bob.Executor, related ...*TileService) error { if len(related) == 0 { return nil } var err error - tileCachedImages1 := TileCachedImageSlice(related) + tileServices1 := TileServiceSlice(related) - _, err = attachArcgisServiceMapArcgisCachedImages0(ctx, exec, len(related), tileCachedImages1, arcgisServiceMap0) + _, err = attachArcgisServiceMapArcgisServices0(ctx, exec, len(related), tileServices1, arcgisServiceMap0) if err != nil { return err } - arcgisServiceMap0.R.ArcgisCachedImages = append(arcgisServiceMap0.R.ArcgisCachedImages, tileCachedImages1...) + arcgisServiceMap0.R.ArcgisServices = append(arcgisServiceMap0.R.ArcgisServices, tileServices1...) for _, rel := range related { rel.R.ArcgisServiceMap = arcgisServiceMap0 @@ -749,13 +749,13 @@ func (o *ArcgisServiceMap) Preload(name string, retrieved any) error { } } return nil - case "ArcgisCachedImages": - rels, ok := retrieved.(TileCachedImageSlice) + case "ArcgisServices": + rels, ok := retrieved.(TileServiceSlice) if !ok { return fmt.Errorf("arcgisServiceMap cannot load %T as %q", retrieved, name) } - o.R.ArcgisCachedImages = rels + o.R.ArcgisServices = rels for _, rel := range rels { if rel != nil { @@ -793,7 +793,7 @@ func buildArcgisServiceMapPreloader() arcgisServiceMapPreloader { type arcgisServiceMapThenLoader[Q orm.Loadable] struct { Account func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ArcgisMapServiceOrganizations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ArcgisCachedImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ArcgisServices func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildArcgisServiceMapThenLoader[Q orm.Loadable]() arcgisServiceMapThenLoader[Q] { @@ -803,8 +803,8 @@ func buildArcgisServiceMapThenLoader[Q orm.Loadable]() arcgisServiceMapThenLoade type ArcgisMapServiceOrganizationsLoadInterface interface { LoadArcgisMapServiceOrganizations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type ArcgisCachedImagesLoadInterface interface { - LoadArcgisCachedImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ArcgisServicesLoadInterface interface { + LoadArcgisServices(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return arcgisServiceMapThenLoader[Q]{ @@ -820,10 +820,10 @@ func buildArcgisServiceMapThenLoader[Q orm.Loadable]() arcgisServiceMapThenLoade return retrieved.LoadArcgisMapServiceOrganizations(ctx, exec, mods...) }, ), - ArcgisCachedImages: thenLoadBuilder[Q]( - "ArcgisCachedImages", - func(ctx context.Context, exec bob.Executor, retrieved ArcgisCachedImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadArcgisCachedImages(ctx, exec, mods...) + ArcgisServices: thenLoadBuilder[Q]( + "ArcgisServices", + func(ctx context.Context, exec bob.Executor, retrieved ArcgisServicesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadArcgisServices(ctx, exec, mods...) }, ), } @@ -945,16 +945,16 @@ func (os ArcgisServiceMapSlice) LoadArcgisMapServiceOrganizations(ctx context.Co return nil } -// LoadArcgisCachedImages loads the arcgisServiceMap's ArcgisCachedImages into the .R struct -func (o *ArcgisServiceMap) LoadArcgisCachedImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadArcgisServices loads the arcgisServiceMap's ArcgisServices into the .R struct +func (o *ArcgisServiceMap) LoadArcgisServices(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.ArcgisCachedImages = nil + o.R.ArcgisServices = nil - related, err := o.ArcgisCachedImages(mods...).All(ctx, exec) + related, err := o.ArcgisServices(mods...).All(ctx, exec) if err != nil { return err } @@ -963,17 +963,17 @@ func (o *ArcgisServiceMap) LoadArcgisCachedImages(ctx context.Context, exec bob. rel.R.ArcgisServiceMap = o } - o.R.ArcgisCachedImages = related + o.R.ArcgisServices = related return nil } -// LoadArcgisCachedImages loads the arcgisServiceMap's ArcgisCachedImages into the .R struct -func (os ArcgisServiceMapSlice) LoadArcgisCachedImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadArcgisServices loads the arcgisServiceMap's ArcgisServices into the .R struct +func (os ArcgisServiceMapSlice) LoadArcgisServices(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - tileCachedImages, err := os.ArcgisCachedImages(mods...).All(ctx, exec) + tileServices, err := os.ArcgisServices(mods...).All(ctx, exec) if err != nil { return err } @@ -983,7 +983,7 @@ func (os ArcgisServiceMapSlice) LoadArcgisCachedImages(ctx context.Context, exec continue } - o.R.ArcgisCachedImages = nil + o.R.ArcgisServices = nil } for _, o := range os { @@ -991,15 +991,18 @@ func (os ArcgisServiceMapSlice) LoadArcgisCachedImages(ctx context.Context, exec continue } - for _, rel := range tileCachedImages { + for _, rel := range tileServices { - if !(o.ArcgisID == rel.ArcgisID) { + if !rel.ArcgisID.IsValue() { + continue + } + if !(rel.ArcgisID.IsValue() && o.ArcgisID == rel.ArcgisID.MustGet()) { continue } rel.R.ArcgisServiceMap = o - o.R.ArcgisCachedImages = append(o.R.ArcgisCachedImages, rel) + o.R.ArcgisServices = append(o.R.ArcgisServices, rel) } } diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index c06b290f..d8416bd6 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -114,6 +114,7 @@ type preloaders struct { Signal signalPreloader Site sitePreloader TileCachedImage tileCachedImagePreloader + TileService tileServicePreloader User userPreloader } @@ -216,6 +217,7 @@ func getPreloaders() preloaders { Signal: buildSignalPreloader(), Site: buildSitePreloader(), TileCachedImage: buildTileCachedImagePreloader(), + TileService: buildTileServicePreloader(), User: buildUserPreloader(), } } @@ -324,6 +326,7 @@ type thenLoaders[Q orm.Loadable] struct { Signal signalThenLoader[Q] Site siteThenLoader[Q] TileCachedImage tileCachedImageThenLoader[Q] + TileService tileServiceThenLoader[Q] User userThenLoader[Q] } @@ -426,6 +429,7 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { Signal: buildSignalThenLoader[Q](), Site: buildSiteThenLoader[Q](), TileCachedImage: buildTileCachedImageThenLoader[Q](), + TileService: buildTileServiceThenLoader[Q](), User: buildUserThenLoader[Q](), } } diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 2aec75f9..cf0a5e2a 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -124,6 +124,7 @@ func Where[Q psql.Filterable]() struct { SpatialRefSys spatialRefSyWhere[Q] StadiaAPIRequests stadiaAPIRequestWhere[Q] TileCachedImages tileCachedImageWhere[Q] + TileServices tileServiceWhere[Q] Users userWhere[Q] } { return struct { @@ -234,6 +235,7 @@ func Where[Q psql.Filterable]() struct { SpatialRefSys spatialRefSyWhere[Q] StadiaAPIRequests stadiaAPIRequestWhere[Q] TileCachedImages tileCachedImageWhere[Q] + TileServices tileServiceWhere[Q] Users userWhere[Q] }{ Addresses: buildAddressWhere[Q](Addresses.Columns), @@ -343,6 +345,7 @@ func Where[Q psql.Filterable]() struct { SpatialRefSys: buildSpatialRefSyWhere[Q](SpatialRefSys.Columns), StadiaAPIRequests: buildStadiaAPIRequestWhere[Q](StadiaAPIRequests.Columns), TileCachedImages: buildTileCachedImageWhere[Q](TileCachedImages.Columns), + TileServices: buildTileServiceWhere[Q](TileServices.Columns), Users: buildUserWhere[Q](Users.Columns), } } diff --git a/db/models/tile.cached_image.bob.go b/db/models/tile.cached_image.bob.go index 12e7af8c..522e246c 100644 --- a/db/models/tile.cached_image.bob.go +++ b/db/models/tile.cached_image.bob.go @@ -22,11 +22,11 @@ import ( // TileCachedImage is an object representing the database table. type TileCachedImage struct { - ArcgisID string `db:"arcgis_id,pk" ` - X int32 `db:"x,pk" ` - Y int32 `db:"y,pk" ` - Z int32 `db:"z,pk" ` - IsEmpty bool `db:"is_empty" ` + X int32 `db:"x,pk" ` + Y int32 `db:"y,pk" ` + Z int32 `db:"z,pk" ` + IsEmpty bool `db:"is_empty" ` + ServiceID int32 `db:"service_id,pk" ` R tileCachedImageR `db:"-" ` } @@ -43,31 +43,31 @@ type TileCachedImagesQuery = *psql.ViewQuery[*TileCachedImage, TileCachedImageSl // tileCachedImageR is where relationships are stored. type tileCachedImageR struct { - ArcgisServiceMap *ArcgisServiceMap // tile.cached_image.cached_image_arcgis_id_fkey + Service *TileService // tile.cached_image.cached_image_service_id_fkey } func buildTileCachedImageColumns(alias string) tileCachedImageColumns { return tileCachedImageColumns{ ColumnsExpr: expr.NewColumnsExpr( - "arcgis_id", "x", "y", "z", "is_empty", + "x", "y", "z", "is_empty", "service_id", ).WithParent("tile.cached_image"), tableAlias: alias, - ArcgisID: psql.Quote(alias, "arcgis_id"), X: psql.Quote(alias, "x"), Y: psql.Quote(alias, "y"), Z: psql.Quote(alias, "z"), IsEmpty: psql.Quote(alias, "is_empty"), + ServiceID: psql.Quote(alias, "service_id"), } } type tileCachedImageColumns struct { expr.ColumnsExpr tableAlias string - ArcgisID psql.Expression X psql.Expression Y psql.Expression Z psql.Expression IsEmpty psql.Expression + ServiceID psql.Expression } func (c tileCachedImageColumns) Alias() string { @@ -82,18 +82,15 @@ func (tileCachedImageColumns) AliasedAs(alias string) tileCachedImageColumns { // All values are optional, and do not have to be set // Generated columns are not included type TileCachedImageSetter struct { - ArcgisID omit.Val[string] `db:"arcgis_id,pk" ` - X omit.Val[int32] `db:"x,pk" ` - Y omit.Val[int32] `db:"y,pk" ` - Z omit.Val[int32] `db:"z,pk" ` - IsEmpty omit.Val[bool] `db:"is_empty" ` + X omit.Val[int32] `db:"x,pk" ` + Y omit.Val[int32] `db:"y,pk" ` + Z omit.Val[int32] `db:"z,pk" ` + IsEmpty omit.Val[bool] `db:"is_empty" ` + ServiceID omit.Val[int32] `db:"service_id,pk" ` } func (s TileCachedImageSetter) SetColumns() []string { vals := make([]string, 0, 5) - if s.ArcgisID.IsValue() { - vals = append(vals, "arcgis_id") - } if s.X.IsValue() { vals = append(vals, "x") } @@ -106,13 +103,13 @@ func (s TileCachedImageSetter) SetColumns() []string { if s.IsEmpty.IsValue() { vals = append(vals, "is_empty") } + if s.ServiceID.IsValue() { + vals = append(vals, "service_id") + } return vals } func (s TileCachedImageSetter) Overwrite(t *TileCachedImage) { - if s.ArcgisID.IsValue() { - t.ArcgisID = s.ArcgisID.MustGet() - } if s.X.IsValue() { t.X = s.X.MustGet() } @@ -125,6 +122,9 @@ func (s TileCachedImageSetter) Overwrite(t *TileCachedImage) { if s.IsEmpty.IsValue() { t.IsEmpty = s.IsEmpty.MustGet() } + if s.ServiceID.IsValue() { + t.ServiceID = s.ServiceID.MustGet() + } } func (s *TileCachedImageSetter) Apply(q *dialect.InsertQuery) { @@ -134,32 +134,32 @@ func (s *TileCachedImageSetter) Apply(q *dialect.InsertQuery) { q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { vals := make([]bob.Expression, 5) - if s.ArcgisID.IsValue() { - vals[0] = psql.Arg(s.ArcgisID.MustGet()) + if s.X.IsValue() { + vals[0] = psql.Arg(s.X.MustGet()) } else { vals[0] = psql.Raw("DEFAULT") } - if s.X.IsValue() { - vals[1] = psql.Arg(s.X.MustGet()) + if s.Y.IsValue() { + vals[1] = psql.Arg(s.Y.MustGet()) } else { vals[1] = psql.Raw("DEFAULT") } - if s.Y.IsValue() { - vals[2] = psql.Arg(s.Y.MustGet()) + if s.Z.IsValue() { + vals[2] = psql.Arg(s.Z.MustGet()) } else { vals[2] = psql.Raw("DEFAULT") } - if s.Z.IsValue() { - vals[3] = psql.Arg(s.Z.MustGet()) + if s.IsEmpty.IsValue() { + vals[3] = psql.Arg(s.IsEmpty.MustGet()) } else { vals[3] = psql.Raw("DEFAULT") } - if s.IsEmpty.IsValue() { - vals[4] = psql.Arg(s.IsEmpty.MustGet()) + if s.ServiceID.IsValue() { + vals[4] = psql.Arg(s.ServiceID.MustGet()) } else { vals[4] = psql.Raw("DEFAULT") } @@ -175,13 +175,6 @@ func (s TileCachedImageSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { func (s TileCachedImageSetter) Expressions(prefix ...string) []bob.Expression { exprs := make([]bob.Expression, 0, 5) - if s.ArcgisID.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "arcgis_id")...), - psql.Arg(s.ArcgisID), - }}) - } - if s.X.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "x")...), @@ -210,15 +203,22 @@ func (s TileCachedImageSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.ServiceID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "service_id")...), + psql.Arg(s.ServiceID), + }}) + } + return exprs } // FindTileCachedImage retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindTileCachedImage(ctx context.Context, exec bob.Executor, ArcgisIDPK string, XPK int32, YPK int32, ZPK int32, cols ...string) (*TileCachedImage, error) { +func FindTileCachedImage(ctx context.Context, exec bob.Executor, ServiceIDPK int32, XPK int32, YPK int32, ZPK int32, cols ...string) (*TileCachedImage, error) { if len(cols) == 0 { return TileCachedImages.Query( - sm.Where(TileCachedImages.Columns.ArcgisID.EQ(psql.Arg(ArcgisIDPK))), + sm.Where(TileCachedImages.Columns.ServiceID.EQ(psql.Arg(ServiceIDPK))), sm.Where(TileCachedImages.Columns.X.EQ(psql.Arg(XPK))), sm.Where(TileCachedImages.Columns.Y.EQ(psql.Arg(YPK))), sm.Where(TileCachedImages.Columns.Z.EQ(psql.Arg(ZPK))), @@ -226,7 +226,7 @@ func FindTileCachedImage(ctx context.Context, exec bob.Executor, ArcgisIDPK stri } return TileCachedImages.Query( - sm.Where(TileCachedImages.Columns.ArcgisID.EQ(psql.Arg(ArcgisIDPK))), + sm.Where(TileCachedImages.Columns.ServiceID.EQ(psql.Arg(ServiceIDPK))), sm.Where(TileCachedImages.Columns.X.EQ(psql.Arg(XPK))), sm.Where(TileCachedImages.Columns.Y.EQ(psql.Arg(YPK))), sm.Where(TileCachedImages.Columns.Z.EQ(psql.Arg(ZPK))), @@ -235,9 +235,9 @@ func FindTileCachedImage(ctx context.Context, exec bob.Executor, ArcgisIDPK stri } // TileCachedImageExists checks the presence of a single record by primary key -func TileCachedImageExists(ctx context.Context, exec bob.Executor, ArcgisIDPK string, XPK int32, YPK int32, ZPK int32) (bool, error) { +func TileCachedImageExists(ctx context.Context, exec bob.Executor, ServiceIDPK int32, XPK int32, YPK int32, ZPK int32) (bool, error) { return TileCachedImages.Query( - sm.Where(TileCachedImages.Columns.ArcgisID.EQ(psql.Arg(ArcgisIDPK))), + sm.Where(TileCachedImages.Columns.ServiceID.EQ(psql.Arg(ServiceIDPK))), sm.Where(TileCachedImages.Columns.X.EQ(psql.Arg(XPK))), sm.Where(TileCachedImages.Columns.Y.EQ(psql.Arg(YPK))), sm.Where(TileCachedImages.Columns.Z.EQ(psql.Arg(ZPK))), @@ -265,7 +265,7 @@ func (o *TileCachedImage) AfterQueryHook(ctx context.Context, exec bob.Executor, // primaryKeyVals returns the primary key values of the TileCachedImage func (o *TileCachedImage) primaryKeyVals() bob.Expression { return psql.ArgGroup( - o.ArcgisID, + o.ServiceID, o.X, o.Y, o.Z, @@ -273,7 +273,7 @@ func (o *TileCachedImage) primaryKeyVals() bob.Expression { } func (o *TileCachedImage) pkEQ() dialect.Expression { - return psql.Group(psql.Quote("tile.cached_image", "arcgis_id"), psql.Quote("tile.cached_image", "x"), psql.Quote("tile.cached_image", "y"), psql.Quote("tile.cached_image", "z")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Group(psql.Quote("tile.cached_image", "service_id"), psql.Quote("tile.cached_image", "x"), psql.Quote("tile.cached_image", "y"), psql.Quote("tile.cached_image", "z")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { return o.primaryKeyVals().WriteSQL(ctx, w, d, start) })) } @@ -300,7 +300,7 @@ func (o *TileCachedImage) Delete(ctx context.Context, exec bob.Executor) error { // Reload refreshes the TileCachedImage using the executor func (o *TileCachedImage) Reload(ctx context.Context, exec bob.Executor) error { o2, err := TileCachedImages.Query( - sm.Where(TileCachedImages.Columns.ArcgisID.EQ(psql.Arg(o.ArcgisID))), + sm.Where(TileCachedImages.Columns.ServiceID.EQ(psql.Arg(o.ServiceID))), sm.Where(TileCachedImages.Columns.X.EQ(psql.Arg(o.X))), sm.Where(TileCachedImages.Columns.Y.EQ(psql.Arg(o.Y))), sm.Where(TileCachedImages.Columns.Z.EQ(psql.Arg(o.Z))), @@ -337,7 +337,7 @@ func (o TileCachedImageSlice) pkIN() dialect.Expression { return psql.Raw("NULL") } - return psql.Group(psql.Quote("tile.cached_image", "arcgis_id"), psql.Quote("tile.cached_image", "x"), psql.Quote("tile.cached_image", "y"), psql.Quote("tile.cached_image", "z")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Group(psql.Quote("tile.cached_image", "service_id"), psql.Quote("tile.cached_image", "x"), psql.Quote("tile.cached_image", "y"), psql.Quote("tile.cached_image", "z")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { pkPairs := make([]bob.Expression, len(o)) for i, row := range o { pkPairs[i] = row.primaryKeyVals() @@ -352,7 +352,7 @@ func (o TileCachedImageSlice) pkIN() dialect.Expression { func (o TileCachedImageSlice) copyMatchingRows(from ...*TileCachedImage) { for i, old := range o { for _, new := range from { - if new.ArcgisID != old.ArcgisID { + if new.ServiceID != old.ServiceID { continue } if new.X != old.X { @@ -462,84 +462,84 @@ func (o TileCachedImageSlice) ReloadAll(ctx context.Context, exec bob.Executor) return nil } -// ArcgisServiceMap starts a query for related objects on arcgis.service_map -func (o *TileCachedImage) ArcgisServiceMap(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisServiceMapsQuery { - return ArcgisServiceMaps.Query(append(mods, - sm.Where(ArcgisServiceMaps.Columns.ArcgisID.EQ(psql.Arg(o.ArcgisID))), +// Service starts a query for related objects on tile.service +func (o *TileCachedImage) Service(mods ...bob.Mod[*dialect.SelectQuery]) TileServicesQuery { + return TileServices.Query(append(mods, + sm.Where(TileServices.Columns.ID.EQ(psql.Arg(o.ServiceID))), )...) } -func (os TileCachedImageSlice) ArcgisServiceMap(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisServiceMapsQuery { - pkArcgisID := make(pgtypes.Array[string], 0, len(os)) +func (os TileCachedImageSlice) Service(mods ...bob.Mod[*dialect.SelectQuery]) TileServicesQuery { + pkServiceID := make(pgtypes.Array[int32], 0, len(os)) for _, o := range os { if o == nil { continue } - pkArcgisID = append(pkArcgisID, o.ArcgisID) + pkServiceID = append(pkServiceID, o.ServiceID) } PKArgExpr := psql.Select(sm.Columns( - psql.F("unnest", psql.Cast(psql.Arg(pkArcgisID), "text[]")), + psql.F("unnest", psql.Cast(psql.Arg(pkServiceID), "integer[]")), )) - return ArcgisServiceMaps.Query(append(mods, - sm.Where(psql.Group(ArcgisServiceMaps.Columns.ArcgisID).OP("IN", PKArgExpr)), + return TileServices.Query(append(mods, + sm.Where(psql.Group(TileServices.Columns.ID).OP("IN", PKArgExpr)), )...) } -func attachTileCachedImageArcgisServiceMap0(ctx context.Context, exec bob.Executor, count int, tileCachedImage0 *TileCachedImage, arcgisServiceMap1 *ArcgisServiceMap) (*TileCachedImage, error) { +func attachTileCachedImageService0(ctx context.Context, exec bob.Executor, count int, tileCachedImage0 *TileCachedImage, tileService1 *TileService) (*TileCachedImage, error) { setter := &TileCachedImageSetter{ - ArcgisID: omit.From(arcgisServiceMap1.ArcgisID), + ServiceID: omit.From(tileService1.ID), } err := tileCachedImage0.Update(ctx, exec, setter) if err != nil { - return nil, fmt.Errorf("attachTileCachedImageArcgisServiceMap0: %w", err) + return nil, fmt.Errorf("attachTileCachedImageService0: %w", err) } return tileCachedImage0, nil } -func (tileCachedImage0 *TileCachedImage) InsertArcgisServiceMap(ctx context.Context, exec bob.Executor, related *ArcgisServiceMapSetter) error { +func (tileCachedImage0 *TileCachedImage) InsertService(ctx context.Context, exec bob.Executor, related *TileServiceSetter) error { var err error - arcgisServiceMap1, err := ArcgisServiceMaps.Insert(related).One(ctx, exec) + tileService1, err := TileServices.Insert(related).One(ctx, exec) if err != nil { return fmt.Errorf("inserting related objects: %w", err) } - _, err = attachTileCachedImageArcgisServiceMap0(ctx, exec, 1, tileCachedImage0, arcgisServiceMap1) + _, err = attachTileCachedImageService0(ctx, exec, 1, tileCachedImage0, tileService1) if err != nil { return err } - tileCachedImage0.R.ArcgisServiceMap = arcgisServiceMap1 + tileCachedImage0.R.Service = tileService1 - arcgisServiceMap1.R.ArcgisCachedImages = append(arcgisServiceMap1.R.ArcgisCachedImages, tileCachedImage0) + tileService1.R.CachedImages = append(tileService1.R.CachedImages, tileCachedImage0) return nil } -func (tileCachedImage0 *TileCachedImage) AttachArcgisServiceMap(ctx context.Context, exec bob.Executor, arcgisServiceMap1 *ArcgisServiceMap) error { +func (tileCachedImage0 *TileCachedImage) AttachService(ctx context.Context, exec bob.Executor, tileService1 *TileService) error { var err error - _, err = attachTileCachedImageArcgisServiceMap0(ctx, exec, 1, tileCachedImage0, arcgisServiceMap1) + _, err = attachTileCachedImageService0(ctx, exec, 1, tileCachedImage0, tileService1) if err != nil { return err } - tileCachedImage0.R.ArcgisServiceMap = arcgisServiceMap1 + tileCachedImage0.R.Service = tileService1 - arcgisServiceMap1.R.ArcgisCachedImages = append(arcgisServiceMap1.R.ArcgisCachedImages, tileCachedImage0) + tileService1.R.CachedImages = append(tileService1.R.CachedImages, tileCachedImage0) return nil } type tileCachedImageWhere[Q psql.Filterable] struct { - ArcgisID psql.WhereMod[Q, string] - X psql.WhereMod[Q, int32] - Y psql.WhereMod[Q, int32] - Z psql.WhereMod[Q, int32] - IsEmpty psql.WhereMod[Q, bool] + X psql.WhereMod[Q, int32] + Y psql.WhereMod[Q, int32] + Z psql.WhereMod[Q, int32] + IsEmpty psql.WhereMod[Q, bool] + ServiceID psql.WhereMod[Q, int32] } func (tileCachedImageWhere[Q]) AliasedAs(alias string) tileCachedImageWhere[Q] { @@ -548,11 +548,11 @@ func (tileCachedImageWhere[Q]) AliasedAs(alias string) tileCachedImageWhere[Q] { func buildTileCachedImageWhere[Q psql.Filterable](cols tileCachedImageColumns) tileCachedImageWhere[Q] { return tileCachedImageWhere[Q]{ - ArcgisID: psql.Where[Q, string](cols.ArcgisID), - X: psql.Where[Q, int32](cols.X), - Y: psql.Where[Q, int32](cols.Y), - Z: psql.Where[Q, int32](cols.Z), - IsEmpty: psql.Where[Q, bool](cols.IsEmpty), + X: psql.Where[Q, int32](cols.X), + Y: psql.Where[Q, int32](cols.Y), + Z: psql.Where[Q, int32](cols.Z), + IsEmpty: psql.Where[Q, bool](cols.IsEmpty), + ServiceID: psql.Where[Q, int32](cols.ServiceID), } } @@ -562,16 +562,16 @@ func (o *TileCachedImage) Preload(name string, retrieved any) error { } switch name { - case "ArcgisServiceMap": - rel, ok := retrieved.(*ArcgisServiceMap) + case "Service": + rel, ok := retrieved.(*TileService) if !ok { return fmt.Errorf("tileCachedImage cannot load %T as %q", retrieved, name) } - o.R.ArcgisServiceMap = rel + o.R.Service = rel if rel != nil { - rel.R.ArcgisCachedImages = TileCachedImageSlice{o} + rel.R.CachedImages = TileCachedImageSlice{o} } return nil default: @@ -580,73 +580,73 @@ func (o *TileCachedImage) Preload(name string, retrieved any) error { } type tileCachedImagePreloader struct { - ArcgisServiceMap func(...psql.PreloadOption) psql.Preloader + Service func(...psql.PreloadOption) psql.Preloader } func buildTileCachedImagePreloader() tileCachedImagePreloader { return tileCachedImagePreloader{ - ArcgisServiceMap: func(opts ...psql.PreloadOption) psql.Preloader { - return psql.Preload[*ArcgisServiceMap, ArcgisServiceMapSlice](psql.PreloadRel{ - Name: "ArcgisServiceMap", + Service: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*TileService, TileServiceSlice](psql.PreloadRel{ + Name: "Service", Sides: []psql.PreloadSide{ { From: TileCachedImages, - To: ArcgisServiceMaps, - FromColumns: []string{"arcgis_id"}, - ToColumns: []string{"arcgis_id"}, + To: TileServices, + FromColumns: []string{"service_id"}, + ToColumns: []string{"id"}, }, }, - }, ArcgisServiceMaps.Columns.Names(), opts...) + }, TileServices.Columns.Names(), opts...) }, } } type tileCachedImageThenLoader[Q orm.Loadable] struct { - ArcgisServiceMap func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Service func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildTileCachedImageThenLoader[Q orm.Loadable]() tileCachedImageThenLoader[Q] { - type ArcgisServiceMapLoadInterface interface { - LoadArcgisServiceMap(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ServiceLoadInterface interface { + LoadService(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return tileCachedImageThenLoader[Q]{ - ArcgisServiceMap: thenLoadBuilder[Q]( - "ArcgisServiceMap", - func(ctx context.Context, exec bob.Executor, retrieved ArcgisServiceMapLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadArcgisServiceMap(ctx, exec, mods...) + Service: thenLoadBuilder[Q]( + "Service", + func(ctx context.Context, exec bob.Executor, retrieved ServiceLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadService(ctx, exec, mods...) }, ), } } -// LoadArcgisServiceMap loads the tileCachedImage's ArcgisServiceMap into the .R struct -func (o *TileCachedImage) LoadArcgisServiceMap(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadService loads the tileCachedImage's Service into the .R struct +func (o *TileCachedImage) LoadService(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.ArcgisServiceMap = nil + o.R.Service = nil - related, err := o.ArcgisServiceMap(mods...).One(ctx, exec) + related, err := o.Service(mods...).One(ctx, exec) if err != nil { return err } - related.R.ArcgisCachedImages = TileCachedImageSlice{o} + related.R.CachedImages = TileCachedImageSlice{o} - o.R.ArcgisServiceMap = related + o.R.Service = related return nil } -// LoadArcgisServiceMap loads the tileCachedImage's ArcgisServiceMap into the .R struct -func (os TileCachedImageSlice) LoadArcgisServiceMap(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadService loads the tileCachedImage's Service into the .R struct +func (os TileCachedImageSlice) LoadService(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - arcgisServiceMaps, err := os.ArcgisServiceMap(mods...).All(ctx, exec) + tileServices, err := os.Service(mods...).All(ctx, exec) if err != nil { return err } @@ -656,15 +656,15 @@ func (os TileCachedImageSlice) LoadArcgisServiceMap(ctx context.Context, exec bo continue } - for _, rel := range arcgisServiceMaps { + for _, rel := range tileServices { - if !(o.ArcgisID == rel.ArcgisID) { + if !(o.ServiceID == rel.ID) { continue } - rel.R.ArcgisCachedImages = append(rel.R.ArcgisCachedImages, o) + rel.R.CachedImages = append(rel.R.CachedImages, o) - o.R.ArcgisServiceMap = rel + o.R.Service = rel break } } diff --git a/db/models/tile.service.bob.go b/db/models/tile.service.bob.go new file mode 100644 index 00000000..fa5d9333 --- /dev/null +++ b/db/models/tile.service.bob.go @@ -0,0 +1,780 @@ +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "fmt" + "io" + + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" +) + +// TileService is an object representing the database table. +type TileService struct { + ID int32 `db:"id,pk" ` + Name string `db:"name" ` + ArcgisID null.Val[string] `db:"arcgis_id" ` + + R tileServiceR `db:"-" ` +} + +// TileServiceSlice is an alias for a slice of pointers to TileService. +// This should almost always be used instead of []*TileService. +type TileServiceSlice []*TileService + +// TileServices contains methods to work with the service table +var TileServices = psql.NewTablex[*TileService, TileServiceSlice, *TileServiceSetter]("tile", "service", buildTileServiceColumns("tile.service")) + +// TileServicesQuery is a query on the service table +type TileServicesQuery = *psql.ViewQuery[*TileService, TileServiceSlice] + +// tileServiceR is where relationships are stored. +type tileServiceR struct { + CachedImages TileCachedImageSlice // tile.cached_image.cached_image_service_id_fkey + ArcgisServiceMap *ArcgisServiceMap // tile.service.service_arcgis_id_fkey +} + +func buildTileServiceColumns(alias string) tileServiceColumns { + return tileServiceColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "id", "name", "arcgis_id", + ).WithParent("tile.service"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + Name: psql.Quote(alias, "name"), + ArcgisID: psql.Quote(alias, "arcgis_id"), + } +} + +type tileServiceColumns struct { + expr.ColumnsExpr + tableAlias string + ID psql.Expression + Name psql.Expression + ArcgisID psql.Expression +} + +func (c tileServiceColumns) Alias() string { + return c.tableAlias +} + +func (tileServiceColumns) AliasedAs(alias string) tileServiceColumns { + return buildTileServiceColumns(alias) +} + +// TileServiceSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type TileServiceSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + Name omit.Val[string] `db:"name" ` + ArcgisID omitnull.Val[string] `db:"arcgis_id" ` +} + +func (s TileServiceSetter) SetColumns() []string { + vals := make([]string, 0, 3) + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.Name.IsValue() { + vals = append(vals, "name") + } + if !s.ArcgisID.IsUnset() { + vals = append(vals, "arcgis_id") + } + return vals +} + +func (s TileServiceSetter) Overwrite(t *TileService) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.Name.IsValue() { + t.Name = s.Name.MustGet() + } + if !s.ArcgisID.IsUnset() { + t.ArcgisID = s.ArcgisID.MustGetNull() + } +} + +func (s *TileServiceSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return TileServices.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 3) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Name.IsValue() { + vals[1] = psql.Arg(s.Name.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if !s.ArcgisID.IsUnset() { + vals[2] = psql.Arg(s.ArcgisID.MustGetNull()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s TileServiceSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s TileServiceSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 3) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.Name.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "name")...), + psql.Arg(s.Name), + }}) + } + + if !s.ArcgisID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_id")...), + psql.Arg(s.ArcgisID), + }}) + } + + return exprs +} + +// FindTileService retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindTileService(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*TileService, error) { + if len(cols) == 0 { + return TileServices.Query( + sm.Where(TileServices.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return TileServices.Query( + sm.Where(TileServices.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(TileServices.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// TileServiceExists checks the presence of a single record by primary key +func TileServiceExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return TileServices.Query( + sm.Where(TileServices.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after TileService is retrieved from the database +func (o *TileService) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = TileServices.AfterSelectHooks.RunHooks(ctx, exec, TileServiceSlice{o}) + case bob.QueryTypeInsert: + ctx, err = TileServices.AfterInsertHooks.RunHooks(ctx, exec, TileServiceSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = TileServices.AfterUpdateHooks.RunHooks(ctx, exec, TileServiceSlice{o}) + case bob.QueryTypeDelete: + ctx, err = TileServices.AfterDeleteHooks.RunHooks(ctx, exec, TileServiceSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the TileService +func (o *TileService) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *TileService) pkEQ() dialect.Expression { + return psql.Quote("tile.service", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the TileService +func (o *TileService) Update(ctx context.Context, exec bob.Executor, s *TileServiceSetter) error { + v, err := TileServices.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single TileService record with an executor +func (o *TileService) Delete(ctx context.Context, exec bob.Executor) error { + _, err := TileServices.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the TileService using the executor +func (o *TileService) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := TileServices.Query( + sm.Where(TileServices.Columns.ID.EQ(psql.Arg(o.ID))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after TileServiceSlice is retrieved from the database +func (o TileServiceSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = TileServices.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = TileServices.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = TileServices.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = TileServices.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o TileServiceSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("tile.service", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o TileServiceSlice) copyMatchingRows(from ...*TileService) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o TileServiceSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return TileServices.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *TileService: + o.copyMatchingRows(retrieved) + case []*TileService: + o.copyMatchingRows(retrieved...) + case TileServiceSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a TileService or a slice of TileService + // then run the AfterUpdateHooks on the slice + _, err = TileServices.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o TileServiceSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return TileServices.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *TileService: + o.copyMatchingRows(retrieved) + case []*TileService: + o.copyMatchingRows(retrieved...) + case TileServiceSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a TileService or a slice of TileService + // then run the AfterDeleteHooks on the slice + _, err = TileServices.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o TileServiceSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals TileServiceSetter) error { + if len(o) == 0 { + return nil + } + + _, err := TileServices.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o TileServiceSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := TileServices.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o TileServiceSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := TileServices.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// CachedImages starts a query for related objects on tile.cached_image +func (o *TileService) CachedImages(mods ...bob.Mod[*dialect.SelectQuery]) TileCachedImagesQuery { + return TileCachedImages.Query(append(mods, + sm.Where(TileCachedImages.Columns.ServiceID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os TileServiceSlice) CachedImages(mods ...bob.Mod[*dialect.SelectQuery]) TileCachedImagesQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return TileCachedImages.Query(append(mods, + sm.Where(psql.Group(TileCachedImages.Columns.ServiceID).OP("IN", PKArgExpr)), + )...) +} + +// ArcgisServiceMap starts a query for related objects on arcgis.service_map +func (o *TileService) ArcgisServiceMap(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisServiceMapsQuery { + return ArcgisServiceMaps.Query(append(mods, + sm.Where(ArcgisServiceMaps.Columns.ArcgisID.EQ(psql.Arg(o.ArcgisID))), + )...) +} + +func (os TileServiceSlice) ArcgisServiceMap(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisServiceMapsQuery { + pkArcgisID := make(pgtypes.Array[null.Val[string]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkArcgisID = append(pkArcgisID, o.ArcgisID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkArcgisID), "text[]")), + )) + + return ArcgisServiceMaps.Query(append(mods, + sm.Where(psql.Group(ArcgisServiceMaps.Columns.ArcgisID).OP("IN", PKArgExpr)), + )...) +} + +func insertTileServiceCachedImages0(ctx context.Context, exec bob.Executor, tileCachedImages1 []*TileCachedImageSetter, tileService0 *TileService) (TileCachedImageSlice, error) { + for i := range tileCachedImages1 { + tileCachedImages1[i].ServiceID = omit.From(tileService0.ID) + } + + ret, err := TileCachedImages.Insert(bob.ToMods(tileCachedImages1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertTileServiceCachedImages0: %w", err) + } + + return ret, nil +} + +func attachTileServiceCachedImages0(ctx context.Context, exec bob.Executor, count int, tileCachedImages1 TileCachedImageSlice, tileService0 *TileService) (TileCachedImageSlice, error) { + setter := &TileCachedImageSetter{ + ServiceID: omit.From(tileService0.ID), + } + + err := tileCachedImages1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachTileServiceCachedImages0: %w", err) + } + + return tileCachedImages1, nil +} + +func (tileService0 *TileService) InsertCachedImages(ctx context.Context, exec bob.Executor, related ...*TileCachedImageSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + tileCachedImages1, err := insertTileServiceCachedImages0(ctx, exec, related, tileService0) + if err != nil { + return err + } + + tileService0.R.CachedImages = append(tileService0.R.CachedImages, tileCachedImages1...) + + for _, rel := range tileCachedImages1 { + rel.R.Service = tileService0 + } + return nil +} + +func (tileService0 *TileService) AttachCachedImages(ctx context.Context, exec bob.Executor, related ...*TileCachedImage) error { + if len(related) == 0 { + return nil + } + + var err error + tileCachedImages1 := TileCachedImageSlice(related) + + _, err = attachTileServiceCachedImages0(ctx, exec, len(related), tileCachedImages1, tileService0) + if err != nil { + return err + } + + tileService0.R.CachedImages = append(tileService0.R.CachedImages, tileCachedImages1...) + + for _, rel := range related { + rel.R.Service = tileService0 + } + + return nil +} + +func attachTileServiceArcgisServiceMap0(ctx context.Context, exec bob.Executor, count int, tileService0 *TileService, arcgisServiceMap1 *ArcgisServiceMap) (*TileService, error) { + setter := &TileServiceSetter{ + ArcgisID: omitnull.From(arcgisServiceMap1.ArcgisID), + } + + err := tileService0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachTileServiceArcgisServiceMap0: %w", err) + } + + return tileService0, nil +} + +func (tileService0 *TileService) InsertArcgisServiceMap(ctx context.Context, exec bob.Executor, related *ArcgisServiceMapSetter) error { + var err error + + arcgisServiceMap1, err := ArcgisServiceMaps.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachTileServiceArcgisServiceMap0(ctx, exec, 1, tileService0, arcgisServiceMap1) + if err != nil { + return err + } + + tileService0.R.ArcgisServiceMap = arcgisServiceMap1 + + arcgisServiceMap1.R.ArcgisServices = append(arcgisServiceMap1.R.ArcgisServices, tileService0) + + return nil +} + +func (tileService0 *TileService) AttachArcgisServiceMap(ctx context.Context, exec bob.Executor, arcgisServiceMap1 *ArcgisServiceMap) error { + var err error + + _, err = attachTileServiceArcgisServiceMap0(ctx, exec, 1, tileService0, arcgisServiceMap1) + if err != nil { + return err + } + + tileService0.R.ArcgisServiceMap = arcgisServiceMap1 + + arcgisServiceMap1.R.ArcgisServices = append(arcgisServiceMap1.R.ArcgisServices, tileService0) + + return nil +} + +type tileServiceWhere[Q psql.Filterable] struct { + ID psql.WhereMod[Q, int32] + Name psql.WhereMod[Q, string] + ArcgisID psql.WhereNullMod[Q, string] +} + +func (tileServiceWhere[Q]) AliasedAs(alias string) tileServiceWhere[Q] { + return buildTileServiceWhere[Q](buildTileServiceColumns(alias)) +} + +func buildTileServiceWhere[Q psql.Filterable](cols tileServiceColumns) tileServiceWhere[Q] { + return tileServiceWhere[Q]{ + ID: psql.Where[Q, int32](cols.ID), + Name: psql.Where[Q, string](cols.Name), + ArcgisID: psql.WhereNull[Q, string](cols.ArcgisID), + } +} + +func (o *TileService) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "CachedImages": + rels, ok := retrieved.(TileCachedImageSlice) + if !ok { + return fmt.Errorf("tileService cannot load %T as %q", retrieved, name) + } + + o.R.CachedImages = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Service = o + } + } + return nil + case "ArcgisServiceMap": + rel, ok := retrieved.(*ArcgisServiceMap) + if !ok { + return fmt.Errorf("tileService cannot load %T as %q", retrieved, name) + } + + o.R.ArcgisServiceMap = rel + + if rel != nil { + rel.R.ArcgisServices = TileServiceSlice{o} + } + return nil + default: + return fmt.Errorf("tileService has no relationship %q", name) + } +} + +type tileServicePreloader struct { + ArcgisServiceMap func(...psql.PreloadOption) psql.Preloader +} + +func buildTileServicePreloader() tileServicePreloader { + return tileServicePreloader{ + ArcgisServiceMap: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*ArcgisServiceMap, ArcgisServiceMapSlice](psql.PreloadRel{ + Name: "ArcgisServiceMap", + Sides: []psql.PreloadSide{ + { + From: TileServices, + To: ArcgisServiceMaps, + FromColumns: []string{"arcgis_id"}, + ToColumns: []string{"arcgis_id"}, + }, + }, + }, ArcgisServiceMaps.Columns.Names(), opts...) + }, + } +} + +type tileServiceThenLoader[Q orm.Loadable] struct { + CachedImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ArcgisServiceMap func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildTileServiceThenLoader[Q orm.Loadable]() tileServiceThenLoader[Q] { + type CachedImagesLoadInterface interface { + LoadCachedImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type ArcgisServiceMapLoadInterface interface { + LoadArcgisServiceMap(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return tileServiceThenLoader[Q]{ + CachedImages: thenLoadBuilder[Q]( + "CachedImages", + func(ctx context.Context, exec bob.Executor, retrieved CachedImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCachedImages(ctx, exec, mods...) + }, + ), + ArcgisServiceMap: thenLoadBuilder[Q]( + "ArcgisServiceMap", + func(ctx context.Context, exec bob.Executor, retrieved ArcgisServiceMapLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadArcgisServiceMap(ctx, exec, mods...) + }, + ), + } +} + +// LoadCachedImages loads the tileService's CachedImages into the .R struct +func (o *TileService) LoadCachedImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.CachedImages = nil + + related, err := o.CachedImages(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Service = o + } + + o.R.CachedImages = related + return nil +} + +// LoadCachedImages loads the tileService's CachedImages into the .R struct +func (os TileServiceSlice) LoadCachedImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + tileCachedImages, err := os.CachedImages(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.CachedImages = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range tileCachedImages { + + if !(o.ID == rel.ServiceID) { + continue + } + + rel.R.Service = o + + o.R.CachedImages = append(o.R.CachedImages, rel) + } + } + + return nil +} + +// LoadArcgisServiceMap loads the tileService's ArcgisServiceMap into the .R struct +func (o *TileService) LoadArcgisServiceMap(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.ArcgisServiceMap = nil + + related, err := o.ArcgisServiceMap(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.ArcgisServices = TileServiceSlice{o} + + o.R.ArcgisServiceMap = related + return nil +} + +// LoadArcgisServiceMap loads the tileService's ArcgisServiceMap into the .R struct +func (os TileServiceSlice) LoadArcgisServiceMap(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + arcgisServiceMaps, err := os.ArcgisServiceMap(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range arcgisServiceMaps { + if !o.ArcgisID.IsValue() { + continue + } + + if !(o.ArcgisID.IsValue() && o.ArcgisID.MustGet() == rel.ArcgisID) { + continue + } + + rel.R.ArcgisServices = append(rel.R.ArcgisServices, o) + + o.R.ArcgisServiceMap = rel + break + } + } + + return nil +} diff --git a/platform/arcgis.go b/platform/arcgis.go index 3d9c407b..46570fe0 100644 --- a/platform/arcgis.go +++ b/platform/arcgis.go @@ -431,6 +431,13 @@ func updateServiceData(ctx context.Context, txn bob.Tx, client *arcgis.ArcGIS, u if err != nil { return fmt.Errorf("save map service: %w", err) } + _, err = models.TileServices.Insert(&models.TileServiceSetter{ + Name: omit.From(sm.Name), + ArcgisID: omitnull.From(sm.ID), + }).One(ctx, txn) + if err != nil { + return fmt.Errorf("save tile service: %w", err) + } } else { return err } diff --git a/platform/tile.go b/platform/tile.go index 128dd0cf..16c890b7 100644 --- a/platform/tile.go +++ b/platform/tile.go @@ -22,18 +22,23 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/platform/oauth" + "github.com/Gleipnir-Technology/nidus-sync/stadia" "github.com/rs/zerolog/log" ) //go:embed empty-tile.png var emptyTileFS embed.FS -func GetTile(ctx context.Context, w http.ResponseWriter, org Organization, z, y, x uint) error { - return getTile(ctx, w, org.model, z, y, x) +func GetTile(ctx context.Context, w http.ResponseWriter, org Organization, use_placeholder bool, z, y, x uint) error { + return getTileFlyover(ctx, w, org.model, use_placeholder, z, y, x) } -func GetTileLatLng(ctx context.Context, w http.ResponseWriter, org *models.Organization, level uint, lat, lng float64) error { +func GetTileFlyoverLatLng(ctx context.Context, w http.ResponseWriter, org *models.Organization, use_placeholder bool, level uint, lat, lng float64) error { y, x := LatLngToTile(level, lat, lng) - return getTile(ctx, w, org, level, y, x) + return getTileFlyover(ctx, w, org, use_placeholder, level, y, x) +} +func GetTileSatelliteLatLng(ctx context.Context, w http.ResponseWriter, level uint, lat, lng float64) error { + y, x := LatLngToTile(level, lat, lng) + return getTileSatellite(ctx, w, level, y, x) } func ImageAtPoint(ctx context.Context, org Organization, level uint, lat, lng float64) (*TileRaster, error) { @@ -84,7 +89,11 @@ func WriteTileRandom(ctx context.Context, w http.ResponseWriter) error { return fmt.Errorf("get tiles: %w", err) } tile_row := tile_rows[rand.Intn(len(tile_rows))] - tile_path := tilePath(tile_row.ArcgisID, uint(tile_row.Z), uint(tile_row.Y), uint(tile_row.X)) + service, err := models.FindTileService(ctx, db.PGInstance.BobDB, tile_row.ServiceID) + if err != nil { + return fmt.Errorf("get service: %w", err) + } + tile_path := tilePath(service.Name, uint(tile_row.Z), uint(tile_row.Y), uint(tile_row.X)) var tile *TileRaster if tile_row.IsEmpty { tile = TileRasterPlaceholder() @@ -97,57 +106,110 @@ func WriteTileRandom(ctx context.Context, w http.ResponseWriter) error { log.Debug().Int32("z", tile_row.Z).Int32("y", tile_row.Y).Int32("x", tile_row.X).Bool("is empty", tile_row.IsEmpty).Msg("random tile") return writeTile(w, tile) } -func getTile(ctx context.Context, w http.ResponseWriter, org *models.Organization, z, y, x uint) error { - if org.ArcgisMapServiceID.IsNull() { - return fmt.Errorf("no map service ID set") - } - map_service_id := org.ArcgisMapServiceID.MustGet() - tile_path := tilePath(map_service_id, z, y, x) - tile_row, err := models.TileCachedImages.Query( - models.SelectWhere.TileCachedImages.ArcgisID.EQ(map_service_id), - models.SelectWhere.TileCachedImages.X.EQ(int32(x)), - models.SelectWhere.TileCachedImages.Y.EQ(int32(y)), - models.SelectWhere.TileCachedImages.Z.EQ(int32(z)), - ).One(ctx, db.PGInstance.BobDB) - if err == nil { - var tile *TileRaster - if tile_row.IsEmpty { - tile = TileRasterPlaceholder() - } else { - tile, err = loadTileFromDisk(tile_path) - if err != nil { - return fmt.Errorf("load tile from disk: %w", err) - } - } - log.Debug().Uint("z", z).Uint("y", y).Uint("x", x).Bool("is empty", tile_row.IsEmpty).Msg("tile from cache") - return writeTile(w, tile) - } - if err.Error() != "sql: no rows in result set" { - return fmt.Errorf("query db: %w", err) - } - image, err := ImageAtTile(ctx, org, uint(z), uint(y), uint(x)) - if err != nil { - return fmt.Errorf("image at tile: %w", err) - } +func cacheImage(ctx context.Context, image *TileRaster, map_service *models.TileService, z, y, x uint) error { + var err error if !image.IsPlaceholder { + tile_path := tilePath(map_service.Name, z, y, x) err = saveTileToDisk(image, tile_path) if err != nil { return fmt.Errorf("save tile: %w", err) } } _, err = models.TileCachedImages.Insert(&models.TileCachedImageSetter{ - ArcgisID: omit.From(map_service_id), - X: omit.From(int32(x)), - Y: omit.From(int32(y)), - Z: omit.From(int32(z)), - IsEmpty: omit.From(image.IsPlaceholder), + ServiceID: omit.From(map_service.ID), + X: omit.From(int32(x)), + Y: omit.From(int32(y)), + Z: omit.From(int32(z)), + IsEmpty: omit.From(image.IsPlaceholder), }).One(ctx, db.PGInstance.BobDB) if err != nil { return fmt.Errorf("save to db: %w", err) } - log.Debug().Uint("z", z).Uint("y", y).Uint("x", x).Bool("placeholder", image.IsPlaceholder).Msg("caching tile") + log.Debug().Str("service", map_service.Name).Uint("z", z).Uint("y", y).Uint("x", x).Bool("placeholder", image.IsPlaceholder).Msg("caching tile") + return nil +} +func getTileCached(ctx context.Context, map_service *models.TileService, z, y, x uint) (*TileRaster, bool, error) { + tile_path := tilePath(map_service.Name, z, y, x) + tile_row, err := models.TileCachedImages.Query( + models.SelectWhere.TileCachedImages.ServiceID.EQ(map_service.ID), + models.SelectWhere.TileCachedImages.X.EQ(int32(x)), + models.SelectWhere.TileCachedImages.Y.EQ(int32(y)), + models.SelectWhere.TileCachedImages.Z.EQ(int32(z)), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + if err.Error() == "sql: no rows in result set" { + return nil, false, nil + } + return nil, false, fmt.Errorf("query db: %w", err) + } + if tile_row.IsEmpty { + return TileRasterPlaceholder(), true, nil + } + tile, err := loadTileFromDisk(tile_path) + if err != nil { + return nil, false, fmt.Errorf("load tile from disk: %w", err) + } + //log.Debug().Uint("z", z).Uint("y", y).Uint("x", x).Bool("is empty", tile_row.IsEmpty).Msg("tile from cache") + return tile, false, nil +} +func getTileFlyover(ctx context.Context, w http.ResponseWriter, org *models.Organization, use_placeholder bool, z, y, x uint) error { + if org.ArcgisMapServiceID.IsNull() { + return fmt.Errorf("no map service ID set") + } + map_service_id := org.ArcgisMapServiceID.MustGet() + map_service, err := models.TileServices.Query( + models.SelectWhere.TileServices.ArcgisID.EQ(map_service_id), + ).One(ctx, db.PGInstance.BobDB) + cached_tile, is_placeholder, err := getTileCached(ctx, map_service, z, y, x) + if err != nil { + return fmt.Errorf("get cached tile: %w", err) + } + if is_placeholder && !use_placeholder { + return fmt.Errorf("only a placeholder is available at %d %d %d", z, y, x) + } + if cached_tile != nil { + return writeTile(w, cached_tile) + } + image, err := ImageAtTile(ctx, org, uint(z), uint(y), uint(x)) + if err != nil { + return fmt.Errorf("image at tile: %w", err) + } + err = cacheImage(ctx, image, map_service, z, y, x) + if err != nil { + return fmt.Errorf("cache image: %w", err) + } return writeTile(w, image) } +func getTileSatellite(ctx context.Context, w http.ResponseWriter, z, y, x uint) error { + map_service_id := "stadia" + map_service, err := models.TileServices.Query( + models.SelectWhere.TileServices.Name.EQ(map_service_id), + ).One(ctx, db.PGInstance.BobDB) + cached_tile, is_placeholder, err := getTileCached(ctx, map_service, z, y, x) + if err != nil { + return fmt.Errorf("get cached tile: %w", err) + } + if is_placeholder { + return fmt.Errorf("only a placeholder is available at %d %d %d", z, y, x) + } + if cached_tile != nil { + return writeTile(w, cached_tile) + } + client := stadia.NewStadiaMaps(config.StadiaMapsAPIKey) + data, err := client.TileRaster(ctx, z, y, x) + if err != nil { + return fmt.Errorf("stadia tile raster: %w", err) + } + tile := TileRaster{ + Content: data, + IsPlaceholder: false, + } + err = cacheImage(ctx, &tile, map_service, z, y, x) + if err != nil { + return fmt.Errorf("cache image: %w", err) + } + return writeTile(w, &tile) +} func imageAtPoint(ctx context.Context, org *models.Organization, level uint, lat, lng float64) (*TileRaster, error) { fssync, err := getFieldseeker(ctx, org) if err != nil { diff --git a/platform/user.go b/platform/user.go index 5bfb86c2..a61cfb7c 100644 --- a/platform/user.go +++ b/platform/user.go @@ -242,7 +242,6 @@ func getUser(ctx context.Context, where mods.Where[*dialect.SelectQuery]) (*User where, ).One(ctx, db.PGInstance.BobDB) if err != nil { - log.Info().Err(err).Msg("getUser failed") if err.Error() == "No such user" || err.Error() == "sql: no rows in result set" { return nil, &NoUserError{} } else if err.Error() == "context canceled" { diff --git a/resource/compliance.go b/resource/compliance.go index 82184517..d8be07c9 100644 --- a/resource/compliance.go +++ b/resource/compliance.go @@ -102,11 +102,13 @@ func imagePoolGet(ctx context.Context, w http.ResponseWriter, public_id string) } level := uint(19) - err = platform.GetTileLatLng(ctx, w, organization, level, pool_feature.Location.Latitude, pool_feature.Location.Longitude) + err = platform.GetTileFlyoverLatLng(ctx, w, organization, false, level, pool_feature.Location.Latitude, pool_feature.Location.Longitude) if err != nil { // Try to get the tile from stadia - - return fmt.Errorf("write tile at %d, %f %f: %w", level, pool_feature.Location.Longitude, pool_feature.Location.Latitude, err) + err = platform.GetTileSatelliteLatLng(ctx, w, level, pool_feature.Location.Latitude, pool_feature.Location.Longitude) + if err != nil { + return fmt.Errorf("write tile at %d, %f %f: %w", level, pool_feature.Location.Longitude, pool_feature.Location.Latitude, err) + } } w.WriteHeader(http.StatusOK) return nil diff --git a/stadia/cmd/tile-raster/main.go b/stadia/cmd/tile-raster/main.go index 80db25c2..995fe50b 100644 --- a/stadia/cmd/tile-raster/main.go +++ b/stadia/cmd/tile-raster/main.go @@ -36,12 +36,12 @@ func main() { client := stadia.NewStadiaMaps(key) ctx := context.Background() - req := stadia.RequestTileRaster{ + req := stadia.RequestTileRasterLatLng{ Latitude: *lat, Longitude: *lng, Zoom: *zoom, } - data, err := client.TileRaster(ctx, req) + data, err := client.TileRasterLatLng(ctx, req) if err != nil { log.Printf("err: %v\n", err) os.Exit(2) diff --git a/stadia/map_tile_raster.go b/stadia/map_tile_raster.go index 3c17bf04..39ff466a 100644 --- a/stadia/map_tile_raster.go +++ b/stadia/map_tile_raster.go @@ -9,20 +9,19 @@ import ( "github.com/rs/zerolog/log" ) -type RequestTileRaster struct { +type RequestTileRasterLatLng struct { Latitude float64 Longitude float64 //Style string Zoom uint } -func (s *StadiaMaps) TileRaster(ctx context.Context, req RequestTileRaster) ([]byte, error) { +func (s *StadiaMaps) TileRaster(ctx context.Context, z, y, x uint) ([]byte, error) { // https://docs.stadiamaps.com/raster/ //url := "https://{urlBase}/tiles/{style}/{z}/{x}/{y}{r}.png" //url := "https://{urlBase}/data/imagery/{z}/{x}/{y}{r}.png" url := "https://{urlBase}/tiles/alidade_satellite/{z}/{x}/{y}.jpg" - y, x := LatLngToTile(req.Zoom, req.Latitude, req.Longitude) //var api_error Error resp, err := s.client.R(). SetContext(ctx). @@ -30,7 +29,7 @@ func (s *StadiaMaps) TileRaster(ctx context.Context, req RequestTileRaster) ([]b //SetPathParam("r", ""). SetPathParam("x", strconv.Itoa(int(x))). SetPathParam("y", strconv.Itoa(int(y))). - SetPathParam("z", strconv.Itoa(int(req.Zoom))). + SetPathParam("z", strconv.Itoa(int(z))). SetPathParam("urlBase", s.urlBaseTiles). SetQueryParam("api_key", s.APIKey). Get(url) @@ -45,6 +44,10 @@ func (s *StadiaMaps) TileRaster(ctx context.Context, req RequestTileRaster) ([]b log.Debug().Str("content_type", content_type).Send() return resp.Bytes(), nil } +func (s *StadiaMaps) TileRasterLatLng(ctx context.Context, req RequestTileRasterLatLng) ([]byte, error) { + y, x := LatLngToTile(req.Zoom, req.Latitude, req.Longitude) + return s.TileRaster(ctx, req.Zoom, y, x) +} // LatLngToTile converts GPS coordinates to ArcGIS tile coordinates func LatLngToTile(level uint, lat, lng float64) (row, column uint) {