mirror of
https://github.com/imjasonh/staticmaps
synced 2026-07-08 19:05:09 +00:00
241 lines
8.7 KiB
Go
241 lines
8.7 KiB
Go
package maps
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"code.google.com/p/go.net/context"
|
|
)
|
|
|
|
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()
|
|
}
|