From c986c5df389ecfc7d82d0872dec2974711f5b4c8 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Tue, 7 Oct 2014 12:23:03 -0400 Subject: [PATCH] Add geocode and reverse geocode --- directions.go | 4 ++ elevation.go | 2 +- geocode.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ maps_test.go | 21 ++++++++ 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 geocode.go diff --git a/directions.go b/directions.go index ab884f5..31a6506 100644 --- a/directions.go +++ b/directions.go @@ -178,6 +178,10 @@ type Bounds struct { Southwest LatLng `json:"southwest"` } +func (b Bounds) String() string { + return fmt.Sprintf("%s|%s", b.Northeast, b.Southwest) +} + type Duration struct { Value int64 `json:"value"` Text string `json:"text"` diff --git a/elevation.go b/elevation.go index 2b532a2..79a2fb7 100644 --- a/elevation.go +++ b/elevation.go @@ -68,6 +68,6 @@ type ElevationResponse struct { Elevation float64 `json:"elevation"` Location LatLng `json:"location"` Resolution float64 `json:"resolution"` - } + } `json:"results"` Status string `json:"status"` } diff --git a/geocode.go b/geocode.go new file mode 100644 index 0000000..ac2bf4d --- /dev/null +++ b/geocode.go @@ -0,0 +1,134 @@ +package maps + +import ( + "fmt" + "net/url" + "strings" +) + +const ( + ComponentRoute = "route" + ComponentLocality = "locality" + ComponentAdministrativeArea = "administrative_area" + ComponentPostalCode = "postal_code" + ComponentCountry = "country" + + LocationTypeRooftop = "ROOFTOP" + LocationTypeRangeInterpolated = "RANGE_INTERPOLATED" + LocationTypeGeometricCenter = "GEOMETRIC_CENTER" + LocationTypeApproximate = "APPROXIMATE" + + // TODO: enums for https://developers.google.com/maps/documentation/geocoding/#Types +) + +func (c Client) Geocode(opts *GeocodeOpts) (*GeocodeResponse, error) { + var r GeocodeResponse + if err := c.doDecode(baseURL+geocode(opts), &r); err != nil { + return nil, err + } + return &r, nil +} + +func geocode(opts *GeocodeOpts) string { + p := url.Values{} + opts.update(p) + return "geocode/json?" + p.Encode() +} + +type GeocodeOpts struct { + Address Address + Components []Component + Language, Region string + Bounds *Bounds +} + +type Component struct { + Key, Value string +} + +func (c Component) encode() string { + return fmt.Sprintf("%s:%s", c.Key, c.Value) +} + +func encodeComponents(cs []Component) string { + s := make([]string, len(cs)) + for i, c := range cs { + s[i] = c.encode() + } + return strings.Join(s, "|") +} + +func (g *GeocodeOpts) update(p url.Values) { + if g == nil { + return + } + if string(g.Address) != "" { + p.Set("address", g.Address.Location()) + } + if g.Components != nil { + p.Set("components", encodeComponents(g.Components)) + } + if g.Language != "" { + p.Set("language", g.Language) + } + if g.Region != "" { + p.Set("region", g.Region) + } + if g.Bounds != nil { + p.Set("bounds", g.Bounds.String()) + } +} + +type GeocodeResponse struct { + Results []struct { + AddressComponents []struct { + LongName string `json:"long_name"` + ShortName string `json:"short_name"` + Types []string `json:"types"` + } `json:"address_components"` + PostcodeLocalities []string `json:"postcode_localities"` + FormattedAddress string `json:"formatted_address"` + Geometry struct { + Location LatLng `json:"location"` + LocationType string `json:"location_type"` + Viewport Bounds `json:"viewport"` + Bounds Bounds `json:"bounds"` + } `json:"geometry"` + Types []string `json:"types"` + PartialMatch string `json:"partial_match"` + } `json:"results"` + Status string `json:"status"` +} + +func (c Client) ReverseGeocode(ll LatLng, opts *ReverseGeocodeOpts) (*GeocodeResponse, error) { + var r GeocodeResponse + if err := c.doDecode(baseURL+reversegeocode(ll, opts), &r); err != nil { + return nil, err + } + return &r, nil +} + +type ReverseGeocodeOpts struct { + Language string + ResultTypes []string + LocationTypes []string +} + +func (r *ReverseGeocodeOpts) update(p url.Values) { + if r == nil { + return + } + if r.ResultTypes != nil { + p.Set("result_type", strings.Join(r.ResultTypes, "|")) + } + if r.LocationTypes != nil { + p.Set("location_type", strings.Join(r.LocationTypes, "|")) + } +} + +func reversegeocode(ll LatLng, opts *ReverseGeocodeOpts) string { + p := url.Values{} + p.Set("latlng", ll.String()) + opts.update(p) + return "geocode/json?" + p.Encode() +} diff --git a/maps_test.go b/maps_test.go index 0fc8e74..68b8d02 100644 --- a/maps_test.go +++ b/maps_test.go @@ -138,3 +138,24 @@ func TestElevation(t *testing.T) { } t.Logf("%v", r) } + +func TestGeocode(t *testing.T) { + c := NewClient("") + opts := &GeocodeOpts{ + Address: Address("1600 Amphitheatre Parkway, Mountain View, CA"), + } + t.Logf("%s", baseURL+geocode(opts)) + r, err := c.Geocode(opts) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + t.Logf("%v", r) + + ll := LatLng{40.714224, -73.961452} + t.Logf("%s", baseURL+reversegeocode(ll, nil)) + r, err = c.ReverseGeocode(ll, nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + t.Logf("%v", r) +}