From a1cc2dbaffe2c602cdb3de6588f775f9e3472ddd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 16 Feb 2026 15:03:26 +0000 Subject: [PATCH] Add district setting page and display of district boundary --- db/dbinfo/import.district.bob.go | 68 ++++--- db/factory/bobfactory_main.bob.go | 2 + db/factory/import.district.bob.go | 162 ++++++++++++--- db/models/import.district.bob.go | 246 ++++++++++++----------- html/static/js/map-district.js | 135 +++++++++++++ html/template/sync/setting-district.html | 136 +++++++++++++ html/template/sync/settings.html | 2 +- sync/routes.go | 1 + sync/setting.go | 60 +++++- sync/url.go | 2 + tools/drop-and-recreate.sql | 2 + 11 files changed, 648 insertions(+), 168 deletions(-) create mode 100644 html/static/js/map-district.js create mode 100644 html/template/sync/setting-district.html diff --git a/db/dbinfo/import.district.bob.go b/db/dbinfo/import.district.bob.go index cefc73ed..27415c50 100644 --- a/db/dbinfo/import.district.bob.go +++ b/db/dbinfo/import.district.bob.go @@ -222,6 +222,24 @@ var ImportDistricts = Table[ Generated: true, AutoIncr: false, }, + Centroid4326: column{ + Name: "centroid_4326", + DBType: "geometry", + Default: "GENERATED", + Comment: "", + Nullable: true, + Generated: true, + AutoIncr: false, + }, + Extent4326: column{ + Name: "extent_4326", + DBType: "geometry", + Default: "GENERATED", + Comment: "", + Nullable: true, + Generated: true, + AutoIncr: false, + }, }, Indexes: importDistrictIndexes{ DistrictPkey: index{ @@ -269,34 +287,36 @@ var ImportDistricts = Table[ } type importDistrictColumns struct { - Gid column - ID column - Website column - Contact column - Address column - Regionid column - PostalCod column - Phone1 column - Fax1 column - Agency column - Code1 column - City1 column - ShapeLeng column - Address2 column - GeneralMG column - City2 column - PostalC1 column - Fax2 column - Phone2 column - ShapeLe1 column - ShapeArea column - Geom column - Geom4326 column + Gid column + ID column + Website column + Contact column + Address column + Regionid column + PostalCod column + Phone1 column + Fax1 column + Agency column + Code1 column + City1 column + ShapeLeng column + Address2 column + GeneralMG column + City2 column + PostalC1 column + Fax2 column + Phone2 column + ShapeLe1 column + ShapeArea column + Geom column + Geom4326 column + Centroid4326 column + Extent4326 column } func (c importDistrictColumns) AsSlice() []column { return []column{ - c.Gid, c.ID, c.Website, c.Contact, c.Address, c.Regionid, c.PostalCod, c.Phone1, c.Fax1, c.Agency, c.Code1, c.City1, c.ShapeLeng, c.Address2, c.GeneralMG, c.City2, c.PostalC1, c.Fax2, c.Phone2, c.ShapeLe1, c.ShapeArea, c.Geom, c.Geom4326, + c.Gid, c.ID, c.Website, c.Contact, c.Address, c.Regionid, c.PostalCod, c.Phone1, c.Fax1, c.Agency, c.Code1, c.City1, c.ShapeLeng, c.Address2, c.GeneralMG, c.City2, c.PostalC1, c.Fax2, c.Phone2, c.ShapeLe1, c.ShapeArea, c.Geom, c.Geom4326, c.Centroid4326, c.Extent4326, } } diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index de6e0aea..767a1715 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -2622,6 +2622,8 @@ func (f *Factory) FromExistingImportDistrict(m *models.ImportDistrict) *ImportDi o.ShapeArea = func() null.Val[decimal.Decimal] { return m.ShapeArea } o.Geom = func() null.Val[string] { return m.Geom } o.Geom4326 = func() null.Val[string] { return m.Geom4326 } + o.Centroid4326 = func() null.Val[string] { return m.Centroid4326 } + o.Extent4326 = func() null.Val[string] { return m.Extent4326 } ctx := context.Background() if m.R.ImportDistrictGidOrganization != nil { diff --git a/db/factory/import.district.bob.go b/db/factory/import.district.bob.go index 3cadd923..73952b69 100644 --- a/db/factory/import.district.bob.go +++ b/db/factory/import.district.bob.go @@ -37,29 +37,31 @@ func (mods ImportDistrictModSlice) Apply(ctx context.Context, n *ImportDistrictT // ImportDistrictTemplate is an object representing the database table. // all columns are optional and should be set by mods type ImportDistrictTemplate struct { - Gid func() int32 - ID func() null.Val[decimal.Decimal] - Website func() null.Val[string] - Contact func() null.Val[string] - Address func() null.Val[string] - Regionid func() null.Val[decimal.Decimal] - PostalCod func() null.Val[decimal.Decimal] - Phone1 func() null.Val[string] - Fax1 func() null.Val[string] - Agency func() null.Val[string] - Code1 func() null.Val[string] - City1 func() null.Val[string] - ShapeLeng func() null.Val[decimal.Decimal] - Address2 func() null.Val[string] - GeneralMG func() null.Val[string] - City2 func() null.Val[string] - PostalC1 func() null.Val[decimal.Decimal] - Fax2 func() null.Val[string] - Phone2 func() null.Val[string] - ShapeLe1 func() null.Val[decimal.Decimal] - ShapeArea func() null.Val[decimal.Decimal] - Geom func() null.Val[string] - Geom4326 func() null.Val[string] + Gid func() int32 + ID func() null.Val[decimal.Decimal] + Website func() null.Val[string] + Contact func() null.Val[string] + Address func() null.Val[string] + Regionid func() null.Val[decimal.Decimal] + PostalCod func() null.Val[decimal.Decimal] + Phone1 func() null.Val[string] + Fax1 func() null.Val[string] + Agency func() null.Val[string] + Code1 func() null.Val[string] + City1 func() null.Val[string] + ShapeLeng func() null.Val[decimal.Decimal] + Address2 func() null.Val[string] + GeneralMG func() null.Val[string] + City2 func() null.Val[string] + PostalC1 func() null.Val[decimal.Decimal] + Fax2 func() null.Val[string] + Phone2 func() null.Val[string] + ShapeLe1 func() null.Val[decimal.Decimal] + ShapeArea func() null.Val[decimal.Decimal] + Geom func() null.Val[string] + Geom4326 func() null.Val[string] + Centroid4326 func() null.Val[string] + Extent4326 func() null.Val[string] r importDistrictR f *Factory @@ -277,6 +279,12 @@ func (o ImportDistrictTemplate) Build() *models.ImportDistrict { if o.Geom4326 != nil { m.Geom4326 = o.Geom4326() } + if o.Centroid4326 != nil { + m.Centroid4326 = o.Centroid4326() + } + if o.Extent4326 != nil { + m.Extent4326 = o.Extent4326() + } o.setModelRels(m) @@ -439,6 +447,8 @@ func (m importDistrictMods) RandomizeAllColumns(f *faker.Faker) ImportDistrictMo ImportDistrictMods.RandomShapeArea(f), ImportDistrictMods.RandomGeom(f), ImportDistrictMods.RandomGeom4326(f), + ImportDistrictMods.RandomCentroid4326(f), + ImportDistrictMods.RandomExtent4326(f), } } @@ -1639,6 +1649,112 @@ func (m importDistrictMods) RandomGeom4326NotNull(f *faker.Faker) ImportDistrict }) } +// Set the model columns to this value +func (m importDistrictMods) Centroid4326(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Centroid4326 = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m importDistrictMods) Centroid4326Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Centroid4326 = f + }) +} + +// Clear any values for the column +func (m importDistrictMods) UnsetCentroid4326() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Centroid4326 = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m importDistrictMods) RandomCentroid4326(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Centroid4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m importDistrictMods) RandomCentroid4326NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Centroid4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m importDistrictMods) Extent4326(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Extent4326 = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m importDistrictMods) Extent4326Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Extent4326 = f + }) +} + +// Clear any values for the column +func (m importDistrictMods) UnsetExtent4326() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Extent4326 = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m importDistrictMods) RandomExtent4326(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Extent4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m importDistrictMods) RandomExtent4326NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { + o.Extent4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + func (m importDistrictMods) WithParentsCascading() ImportDistrictMod { return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { if isDone, _ := importDistrictWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/models/import.district.bob.go b/db/models/import.district.bob.go index 6523d151..725b78e4 100644 --- a/db/models/import.district.bob.go +++ b/db/models/import.district.bob.go @@ -26,29 +26,31 @@ import ( // ImportDistrict is an object representing the database table. type ImportDistrict struct { - Gid int32 `db:"gid,pk" ` - ID null.Val[decimal.Decimal] `db:"id" ` - Website null.Val[string] `db:"website" ` - Contact null.Val[string] `db:"contact" ` - Address null.Val[string] `db:"address" ` - Regionid null.Val[decimal.Decimal] `db:"regionid" ` - PostalCod null.Val[decimal.Decimal] `db:"postal_cod" ` - Phone1 null.Val[string] `db:"phone1" ` - Fax1 null.Val[string] `db:"fax1" ` - Agency null.Val[string] `db:"agency" ` - Code1 null.Val[string] `db:"code1" ` - City1 null.Val[string] `db:"city1" ` - ShapeLeng null.Val[decimal.Decimal] `db:"shape_leng" ` - Address2 null.Val[string] `db:"address2" ` - GeneralMG null.Val[string] `db:"general_mg" ` - City2 null.Val[string] `db:"city2" ` - PostalC1 null.Val[decimal.Decimal] `db:"postal_c_1" ` - Fax2 null.Val[string] `db:"fax2" ` - Phone2 null.Val[string] `db:"phone2" ` - ShapeLe1 null.Val[decimal.Decimal] `db:"shape_le_1" ` - ShapeArea null.Val[decimal.Decimal] `db:"shape_area" ` - Geom null.Val[string] `db:"geom" ` - Geom4326 null.Val[string] `db:"geom_4326,generated" ` + Gid int32 `db:"gid,pk" ` + ID null.Val[decimal.Decimal] `db:"id" ` + Website null.Val[string] `db:"website" ` + Contact null.Val[string] `db:"contact" ` + Address null.Val[string] `db:"address" ` + Regionid null.Val[decimal.Decimal] `db:"regionid" ` + PostalCod null.Val[decimal.Decimal] `db:"postal_cod" ` + Phone1 null.Val[string] `db:"phone1" ` + Fax1 null.Val[string] `db:"fax1" ` + Agency null.Val[string] `db:"agency" ` + Code1 null.Val[string] `db:"code1" ` + City1 null.Val[string] `db:"city1" ` + ShapeLeng null.Val[decimal.Decimal] `db:"shape_leng" ` + Address2 null.Val[string] `db:"address2" ` + GeneralMG null.Val[string] `db:"general_mg" ` + City2 null.Val[string] `db:"city2" ` + PostalC1 null.Val[decimal.Decimal] `db:"postal_c_1" ` + Fax2 null.Val[string] `db:"fax2" ` + Phone2 null.Val[string] `db:"phone2" ` + ShapeLe1 null.Val[decimal.Decimal] `db:"shape_le_1" ` + ShapeArea null.Val[decimal.Decimal] `db:"shape_area" ` + Geom null.Val[string] `db:"geom" ` + Geom4326 null.Val[string] `db:"geom_4326,generated" ` + Centroid4326 null.Val[string] `db:"centroid_4326,generated" ` + Extent4326 null.Val[string] `db:"extent_4326,generated" ` R importDistrictR `db:"-" ` } @@ -71,61 +73,65 @@ type importDistrictR struct { func buildImportDistrictColumns(alias string) importDistrictColumns { return importDistrictColumns{ ColumnsExpr: expr.NewColumnsExpr( - "gid", "id", "website", "contact", "address", "regionid", "postal_cod", "phone1", "fax1", "agency", "code1", "city1", "shape_leng", "address2", "general_mg", "city2", "postal_c_1", "fax2", "phone2", "shape_le_1", "shape_area", "geom", "geom_4326", + "gid", "id", "website", "contact", "address", "regionid", "postal_cod", "phone1", "fax1", "agency", "code1", "city1", "shape_leng", "address2", "general_mg", "city2", "postal_c_1", "fax2", "phone2", "shape_le_1", "shape_area", "geom", "geom_4326", "centroid_4326", "extent_4326", ).WithParent("import.district"), - tableAlias: alias, - Gid: psql.Quote(alias, "gid"), - ID: psql.Quote(alias, "id"), - Website: psql.Quote(alias, "website"), - Contact: psql.Quote(alias, "contact"), - Address: psql.Quote(alias, "address"), - Regionid: psql.Quote(alias, "regionid"), - PostalCod: psql.Quote(alias, "postal_cod"), - Phone1: psql.Quote(alias, "phone1"), - Fax1: psql.Quote(alias, "fax1"), - Agency: psql.Quote(alias, "agency"), - Code1: psql.Quote(alias, "code1"), - City1: psql.Quote(alias, "city1"), - ShapeLeng: psql.Quote(alias, "shape_leng"), - Address2: psql.Quote(alias, "address2"), - GeneralMG: psql.Quote(alias, "general_mg"), - City2: psql.Quote(alias, "city2"), - PostalC1: psql.Quote(alias, "postal_c_1"), - Fax2: psql.Quote(alias, "fax2"), - Phone2: psql.Quote(alias, "phone2"), - ShapeLe1: psql.Quote(alias, "shape_le_1"), - ShapeArea: psql.Quote(alias, "shape_area"), - Geom: psql.Quote(alias, "geom"), - Geom4326: psql.Quote(alias, "geom_4326"), + tableAlias: alias, + Gid: psql.Quote(alias, "gid"), + ID: psql.Quote(alias, "id"), + Website: psql.Quote(alias, "website"), + Contact: psql.Quote(alias, "contact"), + Address: psql.Quote(alias, "address"), + Regionid: psql.Quote(alias, "regionid"), + PostalCod: psql.Quote(alias, "postal_cod"), + Phone1: psql.Quote(alias, "phone1"), + Fax1: psql.Quote(alias, "fax1"), + Agency: psql.Quote(alias, "agency"), + Code1: psql.Quote(alias, "code1"), + City1: psql.Quote(alias, "city1"), + ShapeLeng: psql.Quote(alias, "shape_leng"), + Address2: psql.Quote(alias, "address2"), + GeneralMG: psql.Quote(alias, "general_mg"), + City2: psql.Quote(alias, "city2"), + PostalC1: psql.Quote(alias, "postal_c_1"), + Fax2: psql.Quote(alias, "fax2"), + Phone2: psql.Quote(alias, "phone2"), + ShapeLe1: psql.Quote(alias, "shape_le_1"), + ShapeArea: psql.Quote(alias, "shape_area"), + Geom: psql.Quote(alias, "geom"), + Geom4326: psql.Quote(alias, "geom_4326"), + Centroid4326: psql.Quote(alias, "centroid_4326"), + Extent4326: psql.Quote(alias, "extent_4326"), } } type importDistrictColumns struct { expr.ColumnsExpr - tableAlias string - Gid psql.Expression - ID psql.Expression - Website psql.Expression - Contact psql.Expression - Address psql.Expression - Regionid psql.Expression - PostalCod psql.Expression - Phone1 psql.Expression - Fax1 psql.Expression - Agency psql.Expression - Code1 psql.Expression - City1 psql.Expression - ShapeLeng psql.Expression - Address2 psql.Expression - GeneralMG psql.Expression - City2 psql.Expression - PostalC1 psql.Expression - Fax2 psql.Expression - Phone2 psql.Expression - ShapeLe1 psql.Expression - ShapeArea psql.Expression - Geom psql.Expression - Geom4326 psql.Expression + tableAlias string + Gid psql.Expression + ID psql.Expression + Website psql.Expression + Contact psql.Expression + Address psql.Expression + Regionid psql.Expression + PostalCod psql.Expression + Phone1 psql.Expression + Fax1 psql.Expression + Agency psql.Expression + Code1 psql.Expression + City1 psql.Expression + ShapeLeng psql.Expression + Address2 psql.Expression + GeneralMG psql.Expression + City2 psql.Expression + PostalC1 psql.Expression + Fax2 psql.Expression + Phone2 psql.Expression + ShapeLe1 psql.Expression + ShapeArea psql.Expression + Geom psql.Expression + Geom4326 psql.Expression + Centroid4326 psql.Expression + Extent4326 psql.Expression } func (c importDistrictColumns) Alias() string { @@ -913,29 +919,31 @@ func (importDistrict0 *ImportDistrict) AttachImportDistrictGidOrganization(ctx c } type importDistrictWhere[Q psql.Filterable] struct { - Gid psql.WhereMod[Q, int32] - ID psql.WhereNullMod[Q, decimal.Decimal] - Website psql.WhereNullMod[Q, string] - Contact psql.WhereNullMod[Q, string] - Address psql.WhereNullMod[Q, string] - Regionid psql.WhereNullMod[Q, decimal.Decimal] - PostalCod psql.WhereNullMod[Q, decimal.Decimal] - Phone1 psql.WhereNullMod[Q, string] - Fax1 psql.WhereNullMod[Q, string] - Agency psql.WhereNullMod[Q, string] - Code1 psql.WhereNullMod[Q, string] - City1 psql.WhereNullMod[Q, string] - ShapeLeng psql.WhereNullMod[Q, decimal.Decimal] - Address2 psql.WhereNullMod[Q, string] - GeneralMG psql.WhereNullMod[Q, string] - City2 psql.WhereNullMod[Q, string] - PostalC1 psql.WhereNullMod[Q, decimal.Decimal] - Fax2 psql.WhereNullMod[Q, string] - Phone2 psql.WhereNullMod[Q, string] - ShapeLe1 psql.WhereNullMod[Q, decimal.Decimal] - ShapeArea psql.WhereNullMod[Q, decimal.Decimal] - Geom psql.WhereNullMod[Q, string] - Geom4326 psql.WhereNullMod[Q, string] + Gid psql.WhereMod[Q, int32] + ID psql.WhereNullMod[Q, decimal.Decimal] + Website psql.WhereNullMod[Q, string] + Contact psql.WhereNullMod[Q, string] + Address psql.WhereNullMod[Q, string] + Regionid psql.WhereNullMod[Q, decimal.Decimal] + PostalCod psql.WhereNullMod[Q, decimal.Decimal] + Phone1 psql.WhereNullMod[Q, string] + Fax1 psql.WhereNullMod[Q, string] + Agency psql.WhereNullMod[Q, string] + Code1 psql.WhereNullMod[Q, string] + City1 psql.WhereNullMod[Q, string] + ShapeLeng psql.WhereNullMod[Q, decimal.Decimal] + Address2 psql.WhereNullMod[Q, string] + GeneralMG psql.WhereNullMod[Q, string] + City2 psql.WhereNullMod[Q, string] + PostalC1 psql.WhereNullMod[Q, decimal.Decimal] + Fax2 psql.WhereNullMod[Q, string] + Phone2 psql.WhereNullMod[Q, string] + ShapeLe1 psql.WhereNullMod[Q, decimal.Decimal] + ShapeArea psql.WhereNullMod[Q, decimal.Decimal] + Geom psql.WhereNullMod[Q, string] + Geom4326 psql.WhereNullMod[Q, string] + Centroid4326 psql.WhereNullMod[Q, string] + Extent4326 psql.WhereNullMod[Q, string] } func (importDistrictWhere[Q]) AliasedAs(alias string) importDistrictWhere[Q] { @@ -944,29 +952,31 @@ func (importDistrictWhere[Q]) AliasedAs(alias string) importDistrictWhere[Q] { func buildImportDistrictWhere[Q psql.Filterable](cols importDistrictColumns) importDistrictWhere[Q] { return importDistrictWhere[Q]{ - Gid: psql.Where[Q, int32](cols.Gid), - ID: psql.WhereNull[Q, decimal.Decimal](cols.ID), - Website: psql.WhereNull[Q, string](cols.Website), - Contact: psql.WhereNull[Q, string](cols.Contact), - Address: psql.WhereNull[Q, string](cols.Address), - Regionid: psql.WhereNull[Q, decimal.Decimal](cols.Regionid), - PostalCod: psql.WhereNull[Q, decimal.Decimal](cols.PostalCod), - Phone1: psql.WhereNull[Q, string](cols.Phone1), - Fax1: psql.WhereNull[Q, string](cols.Fax1), - Agency: psql.WhereNull[Q, string](cols.Agency), - Code1: psql.WhereNull[Q, string](cols.Code1), - City1: psql.WhereNull[Q, string](cols.City1), - ShapeLeng: psql.WhereNull[Q, decimal.Decimal](cols.ShapeLeng), - Address2: psql.WhereNull[Q, string](cols.Address2), - GeneralMG: psql.WhereNull[Q, string](cols.GeneralMG), - City2: psql.WhereNull[Q, string](cols.City2), - PostalC1: psql.WhereNull[Q, decimal.Decimal](cols.PostalC1), - Fax2: psql.WhereNull[Q, string](cols.Fax2), - Phone2: psql.WhereNull[Q, string](cols.Phone2), - ShapeLe1: psql.WhereNull[Q, decimal.Decimal](cols.ShapeLe1), - ShapeArea: psql.WhereNull[Q, decimal.Decimal](cols.ShapeArea), - Geom: psql.WhereNull[Q, string](cols.Geom), - Geom4326: psql.WhereNull[Q, string](cols.Geom4326), + Gid: psql.Where[Q, int32](cols.Gid), + ID: psql.WhereNull[Q, decimal.Decimal](cols.ID), + Website: psql.WhereNull[Q, string](cols.Website), + Contact: psql.WhereNull[Q, string](cols.Contact), + Address: psql.WhereNull[Q, string](cols.Address), + Regionid: psql.WhereNull[Q, decimal.Decimal](cols.Regionid), + PostalCod: psql.WhereNull[Q, decimal.Decimal](cols.PostalCod), + Phone1: psql.WhereNull[Q, string](cols.Phone1), + Fax1: psql.WhereNull[Q, string](cols.Fax1), + Agency: psql.WhereNull[Q, string](cols.Agency), + Code1: psql.WhereNull[Q, string](cols.Code1), + City1: psql.WhereNull[Q, string](cols.City1), + ShapeLeng: psql.WhereNull[Q, decimal.Decimal](cols.ShapeLeng), + Address2: psql.WhereNull[Q, string](cols.Address2), + GeneralMG: psql.WhereNull[Q, string](cols.GeneralMG), + City2: psql.WhereNull[Q, string](cols.City2), + PostalC1: psql.WhereNull[Q, decimal.Decimal](cols.PostalC1), + Fax2: psql.WhereNull[Q, string](cols.Fax2), + Phone2: psql.WhereNull[Q, string](cols.Phone2), + ShapeLe1: psql.WhereNull[Q, decimal.Decimal](cols.ShapeLe1), + ShapeArea: psql.WhereNull[Q, decimal.Decimal](cols.ShapeArea), + Geom: psql.WhereNull[Q, string](cols.Geom), + Geom4326: psql.WhereNull[Q, string](cols.Geom4326), + Centroid4326: psql.WhereNull[Q, string](cols.Centroid4326), + Extent4326: psql.WhereNull[Q, string](cols.Extent4326), } } diff --git a/html/static/js/map-district.js b/html/static/js/map-district.js new file mode 100644 index 00000000..9985fab8 --- /dev/null +++ b/html/static/js/map-district.js @@ -0,0 +1,135 @@ +// A test of maplibre-gl in a custom element +class MapDistrict extends HTMLElement { + constructor() { + super(); + + // Create a shadow DOM + this.attachShadow({ mode: "open" }); + + // Initial render + this.render(); + + this._map = null; + + // markers shown on the map + this._markers = []; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + // Initialize the map when the element is added to the DOM + setTimeout(() => this._initializeMap(), 0); + } + + disconnectedCallback() { + if (this._map) { + this._map.remove(); + } + } + + _initializeMap() { + const apiKey = this.getAttribute("api-key"); + const centroid = JSON.parse(this.getAttribute("centroid")); + const csv_file = this.getAttribute("csv-file"); + const district_id = this.getAttribute("district-id"); + const lat = Number(this.getAttribute("latitude") || 36.2); + const lng = Number(this.getAttribute("longitude") || -119.2); + const mapElement = this.shadowRoot.querySelector("#map"); + const tegola = this.getAttribute("tegola"); + const xmin = parseFloat(this.getAttribute("xmin")); + const ymin = parseFloat(this.getAttribute("ymin")); + const xmax = parseFloat(this.getAttribute("xmax")); + const ymax = parseFloat(this.getAttribute("ymax")); + const bounds = [ + [xmin, ymin], + [xmax, ymax], + ]; + console.log("fitting", bounds); + this._map = new maplibregl.Map({ + container: mapElement, + center: centroid.coordinates, + style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json", // Style URL; see our documentation for more options + }).fitBounds(bounds, { + padding: { top: 10, bottom: 10, left: 10, right: 10 }, + }); + this._map.on("load", () => { + this._map.addSource("tegola-nidus", { + type: "vector", + tiles: [ + `${tegola}maps/district/{z}/{x}/{y}?district_id=${district_id}`, + ], + }); + this._map.addLayer({ + id: "bounds", + source: "tegola-nidus", + "source-layer": "bounds", + type: "fill", + paint: { + "fill-opacity": 0.4, + "fill-color": "#dc3545", + }, + }); + }); + } + + // Initial render of component + render() { + this.shadowRoot.innerHTML = ` + + +
+
+
+ `; + } + + addLayer(a) { + return this._map.addLayer(a); + } + addSource(a, b) { + return this._map.addSource(a, b); + } + jumpTo(args) { + return this._map.jumpTo(args); + } + on(a, b) { + return this._map.on(a, b); + } + once(a, b) { + return this._map.once(a, b); + } + queryRenderedFeatures(a) { + return this._map.queryRenderedFeatures(a); + } + + SetLayoutProperty(layout, property, value) { + return this._map.setLayoutProperty(layout, property, value); + } +} + +customElements.define("map-district", MapDistrict); diff --git a/html/template/sync/setting-district.html b/html/template/sync/setting-district.html new file mode 100644 index 00000000..97632f31 --- /dev/null +++ b/html/template/sync/setting-district.html @@ -0,0 +1,136 @@ +{{ template "sync/layout/authenticated.html" . }} + +{{ define "title" }}Settings - Integrations{{ end }} +{{ define "extraheader" }} + + + +{{ end }} +{{ define "content" }} +
+
+
+
+

District Settings

+ +
+ + + +
+ +
+
+
+
Organization Information
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
Contact Information
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
District Coverage
+
+
+
+
+ + +
+
+ + + Hold Ctrl (or Cmd) to select multiple counties +
+
+
+
+
+
+ +
+ + +
+
+
+
+{{ end }} diff --git a/html/template/sync/settings.html b/html/template/sync/settings.html index 4f9a1713..764fc83f 100644 --- a/html/template/sync/settings.html +++ b/html/template/sync/settings.html @@ -101,7 +101,7 @@ Manage your district location and information.

- + Manage District diff --git a/sync/routes.go b/sync/routes.go index 7c9bbcd3..22b8fedb 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -67,6 +67,7 @@ func Router() chi.Router { r.Method("GET", "/pool/upload/{id}", auth.NewEnsureAuth(getPoolUploadByID)) r.Method("POST", "/pool/upload", auth.NewEnsureAuth(postPoolUpload)) r.Method("GET", "/setting", auth.NewEnsureAuth(getSetting)) + r.Method("GET", "/setting/district", auth.NewEnsureAuth(getSettingDistrict)) r.Method("GET", "/setting/integration", auth.NewEnsureAuth(getSettingIntegration)) r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) diff --git a/sync/setting.go b/sync/setting.go index d972f9c7..ba796e5c 100644 --- a/sync/setting.go +++ b/sync/setting.go @@ -3,12 +3,32 @@ package sync import ( "net/http" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/arcgis" + "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/html" + //"github.com/rs/zerolog/log" + "github.com/stephenafamo/scan" ) -type ContentSettingIntegration struct { +type contentDistrict struct { + Centroid string `db:"st_asgeojson"` + GID int32 `db:"gid"` + XMin float32 `db:"st_xmin"` + YMin float32 `db:"st_ymin"` + XMax float32 `db:"st_xmax"` + YMax float32 `db:"st_ymax"` +} +type contentSettingDistrict struct { + District contentDistrict + URL ContentURL + User User +} + +type contentSettingIntegration struct { ArcGISOAuth *models.OauthToken URL ContentURL User User @@ -26,6 +46,42 @@ func getSetting(w http.ResponseWriter, r *http.Request, u *models.User) { } html.RenderOrError(w, "sync/settings.html", data) } +func getSettingDistrict(w http.ResponseWriter, r *http.Request, u *models.User) { + ctx := r.Context() + userContent, err := contentForUser(ctx, u) + if err != nil { + respondError(w, "Failed to get user content", err, http.StatusInternalServerError) + return + } + org, err := u.Organization().One(ctx, db.PGInstance.BobDB) + var district contentDistrict + gid := int32(0) + if org.ImportDistrictGid.IsValue() { + gid = org.ImportDistrictGid.MustGet() + district, err = bob.One[contentDistrict](ctx, db.PGInstance.BobDB, psql.Select( + sm.From("import.district"), + sm.Columns( + "gid", + psql.F("ST_AsGeoJSON", "centroid_4326"), + psql.F("ST_XMin", "extent_4326"), + psql.F("ST_YMin", "extent_4326"), + psql.F("ST_XMax", "extent_4326"), + psql.F("ST_YMax", "extent_4326"), + ), + sm.Where(psql.Quote("gid").EQ(psql.Arg(gid))), + ), scan.StructMapper[contentDistrict]()) + if err != nil { + respondError(w, "Failed to get extents", err, http.StatusInternalServerError) + return + } + } + data := contentSettingDistrict{ + District: district, + URL: newContentURL(), + User: userContent, + } + html.RenderOrError(w, "sync/setting-district.html", data) +} func getSettingIntegration(w http.ResponseWriter, r *http.Request, u *models.User) { ctx := r.Context() userContent, err := contentForUser(ctx, u) @@ -38,7 +94,7 @@ func getSettingIntegration(w http.ResponseWriter, r *http.Request, u *models.Use respondError(w, "Failed to get oauth", err, http.StatusInternalServerError) return } - data := ContentSettingIntegration{ + data := contentSettingIntegration{ ArcGISOAuth: oauth, URL: newContentURL(), User: userContent, diff --git a/sync/url.go b/sync/url.go index 996a1d37..b8ebbb09 100644 --- a/sync/url.go +++ b/sync/url.go @@ -9,6 +9,7 @@ type ContentURL struct { PoolCSVUpload string SamplePoolCSV string Setting string + SettingDistrict string SettingIntegration string SettingPesticide string SettingPesticideAdd string @@ -23,6 +24,7 @@ func newContentURL() ContentURL { PoolCSVUpload: config.MakeURLNidus("/pool/upload"), SamplePoolCSV: config.MakeURLNidus("/static/file/sample-pool.csv"), Setting: config.MakeURLNidus("/setting"), + SettingDistrict: config.MakeURLNidus("/setting/district"), SettingIntegration: config.MakeURLNidus("/setting/integration"), SettingPesticide: config.MakeURLNidus("/setting/pesticide"), SettingPesticideAdd: config.MakeURLNidus("/setting/pesticide/add"), diff --git a/tools/drop-and-recreate.sql b/tools/drop-and-recreate.sql index ebe1470f..d2c37fce 100644 --- a/tools/drop-and-recreate.sql +++ b/tools/drop-and-recreate.sql @@ -18,3 +18,5 @@ GRANT SELECT ON publicreport.report_location TO "tegola"; GRANT ALL PRIVILEGES ON SCHEMA public TO $1; -- do import of district data ALTER TABLE import.district ADD COLUMN geom_4326 geometry(MultiPolygon,4326) GENERATED ALWAYS AS (ST_Transform(geom, 4326)) STORED; +ALTER TABLE import.district ADD COLUMN centroid_4326 geometry(Point,4326) GENERATED ALWAYS AS (ST_Transform(ST_Centroid(geom), 4326)) STORED; +ALTER TABLE import.district ADD COLUMN extent_4326 geometry(Polygon,4326) GENERATED ALWAYS AS (ST_Transform(ST_Envelope(geom), 4326)) STORED;