From 0650345fede8e2eed60bdfa5bf706dcba111141d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=A4ckevik?= Date: Wed, 1 Jul 2026 17:30:15 +0200 Subject: [PATCH 1/2] feat(geocoding): opt-in Permanent tier support Add Permanent bool to ReverseGeocodeRequest and BatchReverseGeocodeRequest. When true, passes permanent=true to the Mapbox API, selecting the Permanent tier which permits storing and caching geocoding results. Defaults to false (Temporary tier) to preserve backward compatibility. --- geocoding_batch_reverse.go | 5 +++++ geocoding_reverse.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/geocoding_batch_reverse.go b/geocoding_batch_reverse.go index 78c5d7e..ab4d904 100644 --- a/geocoding_batch_reverse.go +++ b/geocoding_batch_reverse.go @@ -13,6 +13,9 @@ import ( type BatchReverseGeocodeRequest struct { // Queries are the coordinates to reverse geocode. Must contain 1–1000 items. Queries []ReverseGeocodeQuery + // Permanent requests Permanent tier geocoding, which permits storing results. + // Defaults to Temporary tier when false. + Permanent bool } // ReverseGeocodeQuery is a single query in a batch reverse geocode request. @@ -28,6 +31,7 @@ type batchGeocodeQuery struct { Types string `json:"types"` Longitude float64 `json:"longitude"` Latitude float64 `json:"latitude"` + Permanent bool `json:"permanent"` } // BatchReverseGeocode performs a batch reverse geocode lookup using Geocoding v6 Batch. @@ -53,6 +57,7 @@ func (c *Client) BatchReverseGeocode(ctx context.Context, req *BatchReverseGeoco Types: "address", Longitude: q.Longitude, Latitude: q.Latitude, + Permanent: req.Permanent, } } diff --git a/geocoding_reverse.go b/geocoding_reverse.go index 6ed5918..e378ac2 100644 --- a/geocoding_reverse.go +++ b/geocoding_reverse.go @@ -16,6 +16,9 @@ type ReverseGeocodeRequest struct { Longitude float64 // Latitude is the coordinate to reverse geocode. Latitude float64 + // Permanent requests Permanent tier geocoding, which permits storing results. + // Defaults to Temporary tier when false. + Permanent bool } // ReverseGeocode performs a single reverse geocode lookup using Geocoding v6. @@ -29,6 +32,9 @@ func (c *Client) ReverseGeocode(ctx context.Context, req *ReverseGeocodeRequest) params := url.Values{} params.Set("longitude", strconv.FormatFloat(req.Longitude, 'f', -1, 64)) params.Set("latitude", strconv.FormatFloat(req.Latitude, 'f', -1, 64)) + if req.Permanent { + params.Set("permanent", "true") + } endpoint := c.baseURL + "/search/geocode/v6/reverse?" + params.Encode() httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) From 5d92f14ff83b37881adc9bdd2c08716ccc47578f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=A4ckevik?= Date: Thu, 2 Jul 2026 11:40:54 +0200 Subject: [PATCH 2/2] fix(geocoding): use PointGeometry for Feature and RetrieveFeature GeoJSONGeometry.Coordinates was [][]float64 (LineString), but Geocoding v6 returns Point geometry with a flat [lon, lat] coordinate. JSON decode failed with 'cannot unmarshal number into Go struct field GeoJSONGeometry'. Add PointGeometry with []float64 coordinates and use it for Feature and RetrieveFeature. GeoJSONGeometry (LineString) remains unchanged for map matching. --- geocoding.go | 8 +++++++- geocoding_reverse_test.go | 43 +++++++++++++++++++++++++++++++++++++++ searchbox.go | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/geocoding.go b/geocoding.go index 552c991..f0fe4d8 100644 --- a/geocoding.go +++ b/geocoding.go @@ -6,11 +6,17 @@ type FeatureCollection struct { Features []Feature `json:"features"` } +// PointGeometry is a GeoJSON Point geometry. Coordinates holds [longitude, latitude]. +type PointGeometry struct { + Type string `json:"type"` + Coordinates []float64 `json:"coordinates"` +} + // Feature is a GeoJSON Feature with Mapbox v6 geocoding properties. type Feature struct { Type string `json:"type"` ID string `json:"id"` - Geometry GeoJSONGeometry `json:"geometry"` + Geometry PointGeometry `json:"geometry"` Properties FeatureProperties `json:"properties"` } diff --git a/geocoding_reverse_test.go b/geocoding_reverse_test.go index 405ec34..98d4e9a 100644 --- a/geocoding_reverse_test.go +++ b/geocoding_reverse_test.go @@ -121,6 +121,49 @@ func TestReverseGeocode_ContextParsing(t *testing.T) { } } +func TestReverseGeocode_PointGeometry(t *testing.T) { + // Mapbox returns Point geometry with flat [lon, lat] coordinates. + // Regression: GeoJSONGeometry used [][]float64 (LineString), causing decode to fail. + const body = `{ + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [24.9384, 60.1699]}, + "properties": { + "mapbox_id": "addr.1", + "feature_type": "address", + "full_address": "Mannerheimintie 1, 00100 Helsinki, Finland" + } + }] + }` + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write([]byte(body)); err != nil { + t.Errorf("write response: %v", err) + } + })) + defer srv.Close() + + client := mapbox.NewClient(mapbox.WithAccessToken("t"), mapbox.WithBaseURL(srv.URL)) + result, err := client.ReverseGeocode(context.Background(), &mapbox.ReverseGeocodeRequest{ + Longitude: 24.9384, + Latitude: 60.1699, + }) + if err != nil { + t.Fatalf("ReverseGeocode error: %v", err) + } + if len(result.Features) != 1 { + t.Fatalf("len(Features) = %d, want 1", len(result.Features)) + } + f := result.Features[0] + if f.Geometry.Type != "Point" { + t.Errorf("Geometry.Type = %q, want Point", f.Geometry.Type) + } + if len(f.Geometry.Coordinates) != 2 { + t.Errorf("Geometry.Coordinates len = %d, want 2", len(f.Geometry.Coordinates)) + } +} + func TestReverseGeocode_HTTPError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"message":"Not Authorized - Invalid Token"}`, http.StatusUnauthorized) diff --git a/searchbox.go b/searchbox.go index b52509f..1019b87 100644 --- a/searchbox.go +++ b/searchbox.go @@ -28,7 +28,7 @@ type Suggestion struct { // RetrieveFeature is a GeoJSON Feature returned by /retrieve. type RetrieveFeature struct { Type string `json:"type"` - Geometry GeoJSONGeometry `json:"geometry"` + Geometry PointGeometry `json:"geometry"` Properties RetrieveFeatureProperties `json:"properties"` }