Add centroid information when geocoding

I would use the boundary rect, but I'm getting a 500-level error from
stadia maps
This commit is contained in:
Eli Ribble 2026-02-25 16:08:32 +00:00
parent 8feabbc489
commit 2bb4a134b2
No known key found for this signature in database
10 changed files with 272 additions and 56 deletions

View file

@ -258,6 +258,24 @@ var Organizations = Table[
Generated: true, Generated: true,
AutoIncr: false, AutoIncr: false,
}, },
ServiceAreaCentroidX: column{
Name: "service_area_centroid_x",
DBType: "double precision",
Default: "GENERATED",
Comment: "",
Nullable: true,
Generated: true,
AutoIncr: false,
},
ServiceAreaCentroidY: column{
Name: "service_area_centroid_y",
DBType: "double precision",
Default: "GENERATED",
Comment: "",
Nullable: true,
Generated: true,
AutoIncr: false,
},
}, },
Indexes: organizationIndexes{ Indexes: organizationIndexes{
OrganizationPkey: index{ OrganizationPkey: index{
@ -372,11 +390,13 @@ type organizationColumns struct {
ServiceAreaXmax column ServiceAreaXmax column
ServiceAreaYmax column ServiceAreaYmax column
ServiceAreaCentroidGeojson column ServiceAreaCentroidGeojson column
ServiceAreaCentroidX column
ServiceAreaCentroidY column
} }
func (c organizationColumns) AsSlice() []column { func (c organizationColumns) AsSlice() []column {
return []column{ return []column{
c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, c.LogoUUID, c.Slug, c.GeneralManagerName, c.MailingAddressCity, c.MailingAddressPostalCode, c.MailingAddressStreet, c.OfficeAddressCity, c.OfficeAddressPostalCode, c.OfficeAddressStreet, c.ServiceAreaGeometry, c.ServiceAreaSquareMeters, c.ServiceAreaCentroid, c.ServiceAreaExtent, c.OfficeFax, c.OfficePhone, c.ServiceAreaXmin, c.ServiceAreaYmin, c.ServiceAreaXmax, c.ServiceAreaYmax, c.ServiceAreaCentroidGeojson, c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, c.LogoUUID, c.Slug, c.GeneralManagerName, c.MailingAddressCity, c.MailingAddressPostalCode, c.MailingAddressStreet, c.OfficeAddressCity, c.OfficeAddressPostalCode, c.OfficeAddressStreet, c.ServiceAreaGeometry, c.ServiceAreaSquareMeters, c.ServiceAreaCentroid, c.ServiceAreaExtent, c.OfficeFax, c.OfficePhone, c.ServiceAreaXmin, c.ServiceAreaYmin, c.ServiceAreaXmax, c.ServiceAreaYmax, c.ServiceAreaCentroidGeojson, c.ServiceAreaCentroidX, c.ServiceAreaCentroidY,
} }
} }

View file

@ -2929,6 +2929,8 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization
o.ServiceAreaXmax = func() null.Val[float64] { return m.ServiceAreaXmax } o.ServiceAreaXmax = func() null.Val[float64] { return m.ServiceAreaXmax }
o.ServiceAreaYmax = func() null.Val[float64] { return m.ServiceAreaYmax } o.ServiceAreaYmax = func() null.Val[float64] { return m.ServiceAreaYmax }
o.ServiceAreaCentroidGeojson = func() null.Val[string] { return m.ServiceAreaCentroidGeojson } o.ServiceAreaCentroidGeojson = func() null.Val[string] { return m.ServiceAreaCentroidGeojson }
o.ServiceAreaCentroidX = func() null.Val[float64] { return m.ServiceAreaCentroidX }
o.ServiceAreaCentroidY = func() null.Val[float64] { return m.ServiceAreaCentroidY }
ctx := context.Background() ctx := context.Background()
if len(m.R.EmailContacts) > 0 { if len(m.R.EmailContacts) > 0 {

View file

@ -65,6 +65,8 @@ type OrganizationTemplate struct {
ServiceAreaXmax func() null.Val[float64] ServiceAreaXmax func() null.Val[float64]
ServiceAreaYmax func() null.Val[float64] ServiceAreaYmax func() null.Val[float64]
ServiceAreaCentroidGeojson func() null.Val[string] ServiceAreaCentroidGeojson func() null.Val[string]
ServiceAreaCentroidX func() null.Val[float64]
ServiceAreaCentroidY func() null.Val[float64]
r organizationR r organizationR
f *Factory f *Factory
@ -971,6 +973,12 @@ func (o OrganizationTemplate) Build() *models.Organization {
if o.ServiceAreaCentroidGeojson != nil { if o.ServiceAreaCentroidGeojson != nil {
m.ServiceAreaCentroidGeojson = o.ServiceAreaCentroidGeojson() m.ServiceAreaCentroidGeojson = o.ServiceAreaCentroidGeojson()
} }
if o.ServiceAreaCentroidX != nil {
m.ServiceAreaCentroidX = o.ServiceAreaCentroidX()
}
if o.ServiceAreaCentroidY != nil {
m.ServiceAreaCentroidY = o.ServiceAreaCentroidY()
}
o.setModelRels(m) o.setModelRels(m)
@ -1902,6 +1910,8 @@ func (m organizationMods) RandomizeAllColumns(f *faker.Faker) OrganizationMod {
OrganizationMods.RandomServiceAreaXmax(f), OrganizationMods.RandomServiceAreaXmax(f),
OrganizationMods.RandomServiceAreaYmax(f), OrganizationMods.RandomServiceAreaYmax(f),
OrganizationMods.RandomServiceAreaCentroidGeojson(f), OrganizationMods.RandomServiceAreaCentroidGeojson(f),
OrganizationMods.RandomServiceAreaCentroidX(f),
OrganizationMods.RandomServiceAreaCentroidY(f),
} }
} }
@ -3292,6 +3302,112 @@ func (m organizationMods) RandomServiceAreaCentroidGeojsonNotNull(f *faker.Faker
}) })
} }
// Set the model columns to this value
func (m organizationMods) ServiceAreaCentroidX(val null.Val[float64]) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidX = func() null.Val[float64] { return val }
})
}
// Set the Column from the function
func (m organizationMods) ServiceAreaCentroidXFunc(f func() null.Val[float64]) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidX = f
})
}
// Clear any values for the column
func (m organizationMods) UnsetServiceAreaCentroidX() OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidX = 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 organizationMods) RandomServiceAreaCentroidX(f *faker.Faker) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidX = func() null.Val[float64] {
if f == nil {
f = &defaultFaker
}
val := random_float64(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 organizationMods) RandomServiceAreaCentroidXNotNull(f *faker.Faker) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidX = func() null.Val[float64] {
if f == nil {
f = &defaultFaker
}
val := random_float64(f)
return null.From(val)
}
})
}
// Set the model columns to this value
func (m organizationMods) ServiceAreaCentroidY(val null.Val[float64]) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidY = func() null.Val[float64] { return val }
})
}
// Set the Column from the function
func (m organizationMods) ServiceAreaCentroidYFunc(f func() null.Val[float64]) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidY = f
})
}
// Clear any values for the column
func (m organizationMods) UnsetServiceAreaCentroidY() OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidY = 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 organizationMods) RandomServiceAreaCentroidY(f *faker.Faker) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidY = func() null.Val[float64] {
if f == nil {
f = &defaultFaker
}
val := random_float64(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 organizationMods) RandomServiceAreaCentroidYNotNull(f *faker.Faker) OrganizationMod {
return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) {
o.ServiceAreaCentroidY = func() null.Val[float64] {
if f == nil {
f = &defaultFaker
}
val := random_float64(f)
return null.From(val)
}
})
}
func (m organizationMods) WithParentsCascading() OrganizationMod { func (m organizationMods) WithParentsCascading() OrganizationMod {
return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) {
if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone { if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone {

View file

@ -0,0 +1,3 @@
-- +goose Up
ALTER TABLE organization ADD COLUMN service_area_centroid_x DOUBLE PRECISION GENERATED ALWAYS AS (ST_X(ST_Centroid(service_area_geometry))) STORED;
ALTER TABLE organization ADD COLUMN service_area_centroid_y DOUBLE PRECISION GENERATED ALWAYS AS (ST_Y(ST_Centroid(service_area_geometry))) STORED;

View file

@ -56,6 +56,8 @@ type Organization struct {
ServiceAreaXmax null.Val[float64] `db:"service_area_xmax,generated" ` ServiceAreaXmax null.Val[float64] `db:"service_area_xmax,generated" `
ServiceAreaYmax null.Val[float64] `db:"service_area_ymax,generated" ` ServiceAreaYmax null.Val[float64] `db:"service_area_ymax,generated" `
ServiceAreaCentroidGeojson null.Val[string] `db:"service_area_centroid_geojson,generated" ` ServiceAreaCentroidGeojson null.Val[string] `db:"service_area_centroid_geojson,generated" `
ServiceAreaCentroidX null.Val[float64] `db:"service_area_centroid_x,generated" `
ServiceAreaCentroidY null.Val[float64] `db:"service_area_centroid_y,generated" `
R organizationR `db:"-" ` R organizationR `db:"-" `
@ -118,7 +120,7 @@ type organizationR struct {
func buildOrganizationColumns(alias string) organizationColumns { func buildOrganizationColumns(alias string) organizationColumns {
return organizationColumns{ return organizationColumns{
ColumnsExpr: expr.NewColumnsExpr( ColumnsExpr: expr.NewColumnsExpr(
"id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", "logo_uuid", "slug", "general_manager_name", "mailing_address_city", "mailing_address_postal_code", "mailing_address_street", "office_address_city", "office_address_postal_code", "office_address_street", "service_area_geometry", "service_area_square_meters", "service_area_centroid", "service_area_extent", "office_fax", "office_phone", "service_area_xmin", "service_area_ymin", "service_area_xmax", "service_area_ymax", "service_area_centroid_geojson", "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", "logo_uuid", "slug", "general_manager_name", "mailing_address_city", "mailing_address_postal_code", "mailing_address_street", "office_address_city", "office_address_postal_code", "office_address_street", "service_area_geometry", "service_area_square_meters", "service_area_centroid", "service_area_extent", "office_fax", "office_phone", "service_area_xmin", "service_area_ymin", "service_area_xmax", "service_area_ymax", "service_area_centroid_geojson", "service_area_centroid_x", "service_area_centroid_y",
).WithParent("organization"), ).WithParent("organization"),
tableAlias: alias, tableAlias: alias,
ID: psql.Quote(alias, "id"), ID: psql.Quote(alias, "id"),
@ -148,6 +150,8 @@ func buildOrganizationColumns(alias string) organizationColumns {
ServiceAreaXmax: psql.Quote(alias, "service_area_xmax"), ServiceAreaXmax: psql.Quote(alias, "service_area_xmax"),
ServiceAreaYmax: psql.Quote(alias, "service_area_ymax"), ServiceAreaYmax: psql.Quote(alias, "service_area_ymax"),
ServiceAreaCentroidGeojson: psql.Quote(alias, "service_area_centroid_geojson"), ServiceAreaCentroidGeojson: psql.Quote(alias, "service_area_centroid_geojson"),
ServiceAreaCentroidX: psql.Quote(alias, "service_area_centroid_x"),
ServiceAreaCentroidY: psql.Quote(alias, "service_area_centroid_y"),
} }
} }
@ -181,6 +185,8 @@ type organizationColumns struct {
ServiceAreaXmax psql.Expression ServiceAreaXmax psql.Expression
ServiceAreaYmax psql.Expression ServiceAreaYmax psql.Expression
ServiceAreaCentroidGeojson psql.Expression ServiceAreaCentroidGeojson psql.Expression
ServiceAreaCentroidX psql.Expression
ServiceAreaCentroidY psql.Expression
} }
func (c organizationColumns) Alias() string { func (c organizationColumns) Alias() string {
@ -4449,6 +4455,8 @@ type organizationWhere[Q psql.Filterable] struct {
ServiceAreaXmax psql.WhereNullMod[Q, float64] ServiceAreaXmax psql.WhereNullMod[Q, float64]
ServiceAreaYmax psql.WhereNullMod[Q, float64] ServiceAreaYmax psql.WhereNullMod[Q, float64]
ServiceAreaCentroidGeojson psql.WhereNullMod[Q, string] ServiceAreaCentroidGeojson psql.WhereNullMod[Q, string]
ServiceAreaCentroidX psql.WhereNullMod[Q, float64]
ServiceAreaCentroidY psql.WhereNullMod[Q, float64]
} }
func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] {
@ -4484,6 +4492,8 @@ func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organiz
ServiceAreaXmax: psql.WhereNull[Q, float64](cols.ServiceAreaXmax), ServiceAreaXmax: psql.WhereNull[Q, float64](cols.ServiceAreaXmax),
ServiceAreaYmax: psql.WhereNull[Q, float64](cols.ServiceAreaYmax), ServiceAreaYmax: psql.WhereNull[Q, float64](cols.ServiceAreaYmax),
ServiceAreaCentroidGeojson: psql.WhereNull[Q, string](cols.ServiceAreaCentroidGeojson), ServiceAreaCentroidGeojson: psql.WhereNull[Q, string](cols.ServiceAreaCentroidGeojson),
ServiceAreaCentroidX: psql.WhereNull[Q, float64](cols.ServiceAreaCentroidX),
ServiceAreaCentroidY: psql.WhereNull[Q, float64](cols.ServiceAreaCentroidY),
} }
} }

View file

@ -379,6 +379,7 @@ func errorMissingHeader(ctx context.Context, txn bob.Tx, c *models.FileuploadCSV
return addError(ctx, txn, c, 0, 0, msg) return addError(ctx, txn, c, 0, 0, msg)
} }
func maybeAddServiceArea(req *stadia.StructuredGeocodeRequest, org *models.Organization) { func maybeAddServiceArea(req *stadia.StructuredGeocodeRequest, org *models.Organization) {
/*
if org.ServiceAreaXmax.IsNull() || if org.ServiceAreaXmax.IsNull() ||
org.ServiceAreaYmax.IsNull() || org.ServiceAreaYmax.IsNull() ||
org.ServiceAreaXmin.IsNull() || org.ServiceAreaXmin.IsNull() ||
@ -393,6 +394,14 @@ func maybeAddServiceArea(req *stadia.StructuredGeocodeRequest, org *models.Organ
req.BoundaryRectMaxLat = &ymax req.BoundaryRectMaxLat = &ymax
req.BoundaryRectMinLon = &xmin req.BoundaryRectMinLon = &xmin
req.BoundaryRectMinLat = &ymin req.BoundaryRectMinLat = &ymin
*/
if org.ServiceAreaCentroidX.IsNull() || org.ServiceAreaCentroidY.IsNull() {
return
}
centroid_x := org.ServiceAreaCentroidX.MustGet()
centroid_y := org.ServiceAreaCentroidY.MustGet()
req.FocusPointLat = &centroid_y
req.FocusPointLng = &centroid_x
} }
func parseHeaders(row []string) ([]headerPoolEnum, []string) { func parseHeaders(row []string) ([]headerPoolEnum, []string) {
result_enums := make([]headerPoolEnum, 0) result_enums := make([]headerPoolEnum, 0)

View file

@ -16,6 +16,7 @@ func main() {
boundaryRectMinLat := flag.Float64("boundary-rect-min-lat", 0, "The min lat of the boundary") boundaryRectMinLat := flag.Float64("boundary-rect-min-lat", 0, "The min lat of the boundary")
boundaryRectMaxLon := flag.Float64("boundary-rect-max-lng", 0, "The max lon of the boundary") boundaryRectMaxLon := flag.Float64("boundary-rect-max-lng", 0, "The max lon of the boundary")
boundaryRectMinLon := flag.Float64("boundary-rect-min-lng", 0, "The min lon of the boundary") boundaryRectMinLon := flag.Float64("boundary-rect-min-lng", 0, "The min lon of the boundary")
city := flag.String("city", "", "City address to geocode")
postalCode := flag.String("postal-code", "", "Postal code") postalCode := flag.String("postal-code", "", "Postal code")
focusLat := flag.Float64("focus-lat", 0, "The latitude of the focus point") focusLat := flag.Float64("focus-lat", 0, "The latitude of the focus point")
focusLng := flag.Float64("focus-lng", 0, "The longitude of the focus point") focusLng := flag.Float64("focus-lng", 0, "The longitude of the focus point")
@ -35,23 +36,23 @@ func main() {
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
if focusLat != nil && focusLng == nil { if *focusLat != 0 && *focusLng == 0 {
log.Println("Error: you must specify both focus-lat and focus-lng together, not just focus-lat") log.Println("Error: you must specify both focus-lat and focus-lng together, not just focus-lat")
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
if focusLat == nil && focusLng != nil { if *focusLat == 0 && *focusLng != 0 {
log.Println("Error: you must specify both focus-lat and focus-lng together, not just focus-lng") log.Println("Error: you must specify both focus-lat and focus-lng together, not just focus-lng")
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
if (boundaryRectMaxLat != nil || if (*boundaryRectMaxLat != 0 ||
boundaryRectMinLat != nil || *boundaryRectMinLat != 0 ||
boundaryRectMaxLon != nil || *boundaryRectMaxLon != 0 ||
boundaryRectMinLon != nil) && (boundaryRectMaxLat == nil || *boundaryRectMinLon != 0) && (*boundaryRectMaxLat == 0 ||
boundaryRectMinLat == nil || *boundaryRectMinLat == 0 ||
boundaryRectMaxLon == nil || *boundaryRectMaxLon == 0 ||
boundaryRectMinLon == nil) { *boundaryRectMinLon == 0) {
log.Println("If you specify one of boundary-rect you need to specify them all") log.Println("If you specify one of boundary-rect you need to specify them all")
os.Exit(1) os.Exit(1)
} }
@ -68,16 +69,19 @@ func main() {
Address: address, Address: address,
PostalCode: postalCode, PostalCode: postalCode,
} }
if focusLat != nil && focusLng != nil { if *focusLat != 0 && *focusLng != 0 {
req.FocusPointLat = focusLat req.FocusPointLat = focusLat
req.FocusPointLng = focusLng req.FocusPointLng = focusLng
} }
if boundaryRectMaxLat != nil { if *boundaryRectMaxLat != 0 {
req.BoundaryRectMaxLat = boundaryRectMaxLat req.BoundaryRectMaxLat = boundaryRectMaxLat
req.BoundaryRectMinLat = boundaryRectMinLat req.BoundaryRectMinLat = boundaryRectMinLat
req.BoundaryRectMaxLon = boundaryRectMaxLon req.BoundaryRectMaxLon = boundaryRectMaxLon
req.BoundaryRectMinLon = boundaryRectMinLon req.BoundaryRectMinLon = boundaryRectMinLon
} }
if *city != "" {
req.Locality = city
}
resp, err := client.StructuredGeocode(ctx, req) resp, err := client.StructuredGeocode(ctx, req)
if err != nil { if err != nil {
log.Printf("err: %v\n", err) log.Printf("err: %v\n", err)
@ -86,7 +90,7 @@ func main() {
log.Printf("type: %s, features: %d\n", resp.Type, len(resp.Features)) log.Printf("type: %s, features: %d\n", resp.Type, len(resp.Features))
for i, feature := range resp.Features { for i, feature := range resp.Features {
log.Printf("feature %d: type %s\n", i, feature.Type) log.Printf("feature %d: type %s\n", i, feature.Type)
log.Printf("\tgeometry %s\n", feature.Geometry.Type) log.Printf("\tgeometry %s (%f %f)\n", feature.Geometry.Type, feature.Geometry.Coordinates[0], feature.Geometry.Coordinates[1])
log.Printf("\tproperties %s\n", feature.Properties.Layer) log.Printf("\tproperties %s\n", feature.Properties.Layer)
} }
} }

80
stadia/error.go Normal file
View file

@ -0,0 +1,80 @@
package stadia
import (
"encoding/json"
"fmt"
"io"
"resty.dev/v3"
)
// Unfortunately, Stadia Maps is inconsistent in how it handles errors.
// We therefore have to have a function that handles all the different JSON
// error variations.
func parseError(resp *resty.Response) error {
content, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading all body: %w", err)
}
var server_error serverError
err = json.Unmarshal(content, &server_error)
if err == nil {
return newAPIError(resp.StatusCode(), server_error.Error.Reason)
}
// At this point we've exhausted all of our options, so just pass the JSON through
return newAPIError(resp.StatusCode(), string(content))
}
type apiError struct {
Message string
Status int
}
func newAPIError(status int, msg string) apiError {
return apiError{
Message: msg,
Status: status,
}
}
func (e apiError) Error() string {
return e.Message
}
type Error struct {
ErrorMessage string `json:"error"`
Errors []string `json:"errors"`
}
func (e *Error) Error() string {
return e.ErrorMessage
}
/*
Got this when I managed to bork the server
{
"error": {
"reason": "Internal Server Error"
},
"status": 500
}
*/
type errorWithReason struct {
Reason string `json:"reason"`
}
type serverError struct {
Error errorWithReason `json:"error"`
Status int `json:"status"`
}
/*
if len(result.Geocode.Errors) > 0 {
joined := strings.Join(result.Geocode.Errors, ", ")
return nil, fmt.Errorf("structured geocoding failure: %d '%s'", resp.StatusCode(), joined)
} else if result.Geocode.Error != "" {
return nil, fmt.Errorf("structured geocoding failure: %d '%s'", resp.StatusCode(), result.Geocode.Error)
} else {
return nil, fmt.Errorf("structured geocoding failure: %d", resp.StatusCode())
}
*/

View file

@ -1,18 +1,9 @@
package stadia package stadia
type Error struct {
ErrorMessage string `json:"error"`
Errors []string `json:"errors"`
}
func (e *Error) Error() string {
return e.ErrorMessage
}
// GeocodeResponse represents the top-level response from the geocoding API // GeocodeResponse represents the top-level response from the geocoding API
type GeocodeResponse struct { type GeocodeResponse struct {
BBox []float64 `json:"bbox"` // [W, S, E, N] BBox []float64 `json:"bbox"` // [W, S, E, N]
ErrorMessage string `json:"error"` ErrorMessage string `json:"error,omitempty"`
Features []GeocodeFeature `json:"features"` Features []GeocodeFeature `json:"features"`
Geocode GeocodeMeta `json:"geocoding"` Geocode GeocodeMeta `json:"geocoding"`
Type string `json:"type"` // Should be "FeatureCollection" Type string `json:"type"` // Should be "FeatureCollection"

View file

@ -3,7 +3,6 @@ package stadia
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
) )
@ -58,7 +57,6 @@ func (s *StadiaMaps) StructuredGeocode(ctx context.Context, req StructuredGeocod
resp, err := s.client.R(). resp, err := s.client.R().
SetQueryParamsFromValues(query). SetQueryParamsFromValues(query).
SetContext(ctx). SetContext(ctx).
SetError(&result).
SetResult(&result). SetResult(&result).
SetPathParam("urlBase", s.urlBase). SetPathParam("urlBase", s.urlBase).
SetQueryParam("api_key", s.APIKey). SetQueryParam("api_key", s.APIKey).
@ -68,24 +66,7 @@ func (s *StadiaMaps) StructuredGeocode(ctx context.Context, req StructuredGeocod
} }
if !resp.IsSuccess() { if !resp.IsSuccess() {
/* return nil, parseError(resp)
if api_error.Error() != "" {
return nil, &api_error
}
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read all failure: %w", err)
}
*/
fmt.Printf("geocoding error: %s\n", result.Geocode.Error)
if len(result.Geocode.Errors) > 0 {
joined := strings.Join(result.Geocode.Errors, ", ")
return nil, fmt.Errorf("structured geocoding failure: %d '%s'", resp.StatusCode(), joined)
} else if result.Geocode.Error != "" {
return nil, fmt.Errorf("structured geocoding failure: %d '%s'", resp.StatusCode(), result.Geocode.Error)
} else {
return nil, fmt.Errorf("structured geocoding failure: %d", resp.StatusCode())
}
} }
return &result, nil return &result, nil
} }