1
0
Fork 0
mirror of https://github.com/imjasonh/staticmaps synced 2026-07-08 02:45:10 +00:00
staticmaps/geocode.go
2021-07-27 11:22:55 -04:00

240 lines
8.6 KiB
Go

package maps
import (
"context"
"fmt"
"net/url"
"strings"
)
const (
// ComponentRoute matches long or short name of a route.
ComponentRoute = "route"
// ComponentLocality matches against most locality and sublocality types.
ComponentLocality = "locality"
// ComponentAdministrativeArea matches all the administrative area levels.
ComponentAdministrativeArea = "administrative_area"
// ComponentPostalCode matches postcal code and postal code prefix.
ComponentPostalCode = "postal_code"
// ComponentCountry matches a country name or a two-letter ISO 3166-1 country code.
ComponentCountry = "country"
// LocationTypeRooftop restricts the results to addresses for which we have location information accurate down to street address precision.
LocationTypeRooftop = "ROOFTOP"
// LocationTypeRangeInterpolated restricts the results to those that reflect an approximation (usually a road) interpolated between two precise points (such as intersections).
LocationTypeRangeInterpolated = "RANGE_INTERPOLATED"
// LocationTypeGeometricCenter restricts the results to geometric centers of a location such as a polyline (for example, a street) or polygon (region).
LocationTypeGeometricCenter = "GEOMETRIC_CENTER"
// LocationTypeApproximate restricts the results to those that are characterized as approximate.
LocationTypeApproximate = "APPROXIMATE"
// TODO: enums for https://developers.google.com/maps/documentation/geocoding/#Types
)
// Geocode requests conversion of an address to a latitude/longitude pair.
func Geocode(ctx context.Context, opts *GeocodeOpts) ([]GeocodeResult, error) {
var r geocodeResponse
if err := doDecode(ctx, baseURL+geocode(opts), &r); err != nil {
return nil, err
}
if r.Status != StatusOK {
return nil, APIError{r.Status, ""}
}
return r.Results, nil
}
func geocode(opts *GeocodeOpts) string {
p := url.Values{}
opts.update(p)
return "geocode/json?" + p.Encode()
}
// GeocodeOpts defines options for Geocode requests.
type GeocodeOpts struct {
// The address to geocode.
Address Address
// Components specifies a component filter for which you wish to obtain a geocode.
//
// Only results that match all filters will be returned. Filter values support the same methods of spelling correction and partial matching as other geocoding requests.
//
// See https://developers.google.com/maps/documentation/geocoding/#ComponentFiltering
Components []Component
// The language in which to return results.
//
// See https://developers.google.com/maps/faq#languagesupport
Language string
// Region specifies a region to bias results toward, specified as a ccTLD ("top-level domain" two-character value.
//
// This will influence, not fully restrict, results from the geocoder.
//
// See https://developers.google.com/maps/documentation/geocoding/#RegionCodes
Region string
// Bounds specifies the bounding box of the viewport within which to bias geocode results more prominently.
//
// This will influence, not fully restrict, results from the geocoder.
Bounds *Bounds
}
// Component describes a single component filter.
//
// See https://developers.google.com/maps/documentation/geocoding/#ComponentFiltering
type Component struct {
// Key is the type of component to filter on.
//
// Accepted values are ComponentRoute, ComponentLocality, ComponentAdministrativeArea, ComponentPostalCode and ComponentCountry.
Key string
// Value is the value of the filter to enforce.
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 []GeocodeResult `json:"results"`
Status string `json:"status"`
}
// GeocodeResult represents the geocoded result for the given input.
type GeocodeResult struct {
// AddressComponents contains the separate address components.
AddressComponents []struct {
// LongName is the full text description or name of the address component returned by the geocoder.
LongName string `json:"long_name"`
// ShortName is the abbreviated textual name for the address component, if available.
//
// For example, an address component for the state of Alaska may have a LongName of "Alaska" and a ShortName of "AK" using the 2-letter postal abbreviation.
ShortName string `json:"short_name"`
// Types contains the types of address components.
Types []string `json:"types"`
} `json:"address_components"`
// PostcodeLocalities denotes all the localities contained in a postal code.
//
// This is only present when the result is a postal code that contains multiple localities.
PostcodeLocalities []string `json:"postcode_localities"`
// FormattedAddress contains the human-readable address of this location.
//
// Often this is equivalent to the "postal address" which sometimes differs from country to country.
FormattedAddress string `json:"formatted_address"`
// Geometry contains information about the location.
Geometry struct {
// Location is the geocoded latitude and longitude of the location.
Location LatLng `json:"location"`
// LocationType specifies additional data about the specified location.
//
// Its value will be one of:
// - "ROOFTOP" indicating that the returned result is a precise geocode for which we have location information accurate down to street address precision.
// - "RANGE_INTERPOLATED" indicating that the returned result reflects and approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.
// - "GEOMETRIC_CENTER" indicating that the returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).
LocationType string `json:"location_type"`
// Viewport contains the recommended viewport bounding box for displaying the returned result.
Viewport Bounds `json:"viewport"`
// Bounds, if included, contains the bounding box which can fully contain the returned result.
//
// Note that this may not match Viewport.
Bounds Bounds `json:"bounds"`
} `json:"geometry"`
// Types indicates the types of the returned result.
//
// This contains a set of zero or more tags identifying the type of feature returned in a result.
// For example, a geocode of "Chicago" returns "locality" which indicates that "Chicago" is a city,
// and also returns "political" which indicates it is a political entity.
Types []string `json:"types"`
// PartialMatch indicates that the geocoder did not return an exact match for the original request,
// though it was able to match part of the requested address. You may wish to examine the original
// request for misspellings and/or an incomplete address.
PartialMatch string `json:"partial_match"`
}
// ReverseGeocode requests conversion of a location to its nearest address.
//
// See https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding
func ReverseGeocode(ctx context.Context, ll LatLng, opts *ReverseGeocodeOpts) ([]GeocodeResult, error) {
var r geocodeResponse
if err := doDecode(ctx, baseURL+reversegeocode(ll, opts), &r); err != nil {
return nil, err
}
if r.Status != StatusOK {
return nil, APIError{r.Status, ""}
}
return r.Results, nil
}
// ReverseGeocodeOpts defines options for ReverseGeocode requests.
type ReverseGeocodeOpts struct {
// The language in which to return results.
//
// See https://developers.google.com/maps/faq#languagesupport
Language string
// ResultTypes specifies filters on result types, e.g., "country", "street address"
ResultTypes []string
// LocationTypes specifies filters on location types.
//
// Accepted values are LocationTypeRooftop, LocationTypeRangeInterpolated, LocationTypeGeometricCenter and LocationTypeApproximate.
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()
}