Skip to content

Commit

Permalink
feat(timezone): added interface to fetch Timezone info via gmaps api (#6
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mattevans authored Aug 31, 2022
1 parent 4e5fca7 commit fa05eb5
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 194 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
sudo: false
language: go
go:
- 1.16.x
- tip
- 1.19.x
61 changes: 37 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/mattevans/abode)](https://goreportcard.com/report/github.com/mattevans/abode)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/mattevans/abode/blob/master/LICENSE)

Explode one-line address strings using Golang.

This package uses the [Google Maps API](https://console.developers.google.com/apis/credentials) to geocode the address.
Specifically you will require the [Geocoding API](https://console.developers.google.com/apis/library/geocoding-backend.googleapis.com)
enabled to translate address strings to detailed address objects.

Don't forget to set your `GOOGLE_MAPS_API_KEY` environment variable.
- Geocode one-line addresses.
- Determine timezone information for a given address.
- This package uses the [Google Maps Web Services](https://developers.google.com/maps/web-services) to geocode the address.
- You will require the [Geocoding API](https://developers.google.com/maps/documentation) enabled, and optionally the [Timezone API] if you wish to also use `Timezone()`.
- Remember to set your `GOOGLE_MAPS_API_KEY` environment variable.

Installation
-----------------
Expand All @@ -21,39 +19,54 @@ Installation
Example
-------------

Explode your one-line address...
### Geocode an address:

```go
yourAddress := "193 Rogers Ave, Brooklyn, New York"
addr := "193 Rogers Ave, Brooklyn, New York"

// Explode our one-line address into components.
address, err := abode.Explode(yourAddress)
address, err := abode.ExplodeWithContext(ctx, addr)
if err != nil {
return err
}
```

Which will give you...
Returns...

```go
abode.Address{
AddressLine1: "193 Rogers Avenue",
AddressLine2: "Brooklyn"
AddressCity: nil,
AddressState: "New York"
AddressCountry: "United States"
AddressZip: "11216"
AddressLat: 40.6706073,
AddressLng: -73.9530182,
FormattedAddress: "193 Rogers Ave, Brooklyn, NY 11216, USA",
AddressLine1: "193 Rogers Avenue",
AddressLine2: "Brooklyn"
AddressCity: nil,
AddressState: "New York"
AddressCountry: "United States"
AddressZip: "11216"
AddressLat: 40.6706073,
AddressLng: -73.9530182,
FormattedAddress: "193 Rogers Ave, Brooklyn, NY 11216, USA",
}
```

Configuration
-------------
### Timezone information for an address:

Each `abode.Address{}` component can be tailored to meet your needs. Simply adjust the mapping of the Google Maps Address Components [here](https://github.com/mattevans/abode/blob/master/component.go#L31).
```go
addr := "193 Rogers Ave, Brooklyn, New York"

address, err := abode.Timezone(ctx, addr)
if err != nil {
return err
}
```

Returns...

```go
abode.Location{
DstOffset: 0,
RawOffset: -17762,
TimeZoneId: "GMT-04:56:02",
TimeZoneName: "America/New_York"
}
```

Disclaimer
-------------
Expand Down
111 changes: 82 additions & 29 deletions abode.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"googlemaps.github.io/maps"
)

var client *maps.Client
var addressLineDelimiter = ","
var (
client *maps.Client
addressLineDelimiter = ","
)

// Address represents a response Address from abode.
type Address struct {
Expand All @@ -27,47 +29,62 @@ type Address struct {
FormattedAddress *string `json:"formatted_address"`
}

// initClient will initalize a Google Maps API client.
func initClient() error {
var err error
key := os.Getenv("GOOGLE_MAPS_API_KEY")
type Location struct {
DstOffset int `json:"dst_offset"`
RawOffset int `json:"raw_offset"`
TimeZoneId string `json:"time_zone_id"`
TimeZoneName string `json:"time_zone_name"`
}

// client will initialize a Google Maps API client.
func mapsClient() error {
var (
err error
key = os.Getenv("GOOGLE_MAPS_API_KEY")
)

if key == "" {
return errors.New("please configure a `GOOGLE_MAPS_API_KEY`")
return errors.New("missing `GOOGLE_MAPS_API_KEY`")
}

client, err = maps.NewClient(maps.WithAPIKey(key))
return err
}

// Explode takes a one-line address string, explodes it and returns an *Address
// Explode takes a one-line address string, explodes it using gmaps.Geocode() and returns an *Address.
// Deprecated: Use ExplodeWithContext instead.
func Explode(address string) (*Address, error) {
return ExplodeWithContext(context.Background(), address)
}

// ExplodeWithContext takes a one-line address string, explodes it using gmaps.Geocode() and returns an *Address.
func ExplodeWithContext(ctx context.Context, address string) (*Address, error) {
if client == nil {
if err := initClient(); err != nil {
if err := mapsClient(); err != nil {
return nil, err
}
}

// Build the API request.
req := &maps.GeocodingRequest{
rsp, err := client.Geocode(ctx, &maps.GeocodingRequest{
Address: address,
}

// Execute the request.
resp, err := client.Geocode(context.Background(), req)
if len(resp) < 1 {
})
if err != nil {
return nil, err
}
if err != nil {

if len(rsp) < 1 {
return nil, err
}

// Using the first match in our response, grab the values we need.
components := resp[0].AddressComponents
formattedAddress := resp[0].FormattedAddress
lat := resp[0].Geometry.Location.Lat
lng := resp[0].Geometry.Location.Lng
var (
geocodeResult = rsp[0]
components = geocodeResult.AddressComponents
formattedAddress = geocodeResult.FormattedAddress
lat = geocodeResult.Geometry.Location.Lat
lng = geocodeResult.Geometry.Location.Lng
)

// Construct the return *Address{}
response := &Address{
return &Address{
AddressLine1: compose(addressLine1Composition, "", components, false),
AddressLine2: compose(addressLine2Composition, addressLineDelimiter, components, false),
AddressCity: compose(addressCityComposition, addressLineDelimiter, components, false),
Expand All @@ -78,28 +95,64 @@ func Explode(address string) (*Address, error) {
AddressLat: &lat,
AddressLng: &lng,
FormattedAddress: &formattedAddress,
}, err
}

// Timezone takes a one-line address string, and determine timezone/location data for it using gmaps.Timezone().
func Timezone(ctx context.Context, address string) (*Location, error) {
if client == nil {
if err := mapsClient(); err != nil {
return nil, err
}
}

return response, err
geocodeResult, err := ExplodeWithContext(ctx, address)
if err != nil {
return nil, err
}

if geocodeResult.AddressLat == nil || geocodeResult.AddressLng == nil {
return nil, errors.New("unable to determine latitude and longitude for address")
}

resp, err := client.Timezone(ctx, &maps.TimezoneRequest{
Location: &maps.LatLng{Lat: *geocodeResult.AddressLat, Lng: *geocodeResult.AddressLng},
})
if err != nil {
return nil, err
}

return &Location{
DstOffset: resp.DstOffset,
RawOffset: resp.RawOffset,
TimeZoneId: resp.TimeZoneID,
TimeZoneName: resp.TimeZoneName,
}, err
}

func compose(composition []string, delimiter string, components []maps.AddressComponent, useShortName bool) *string {
var str string

for _, element := range composition {
component := getComponentByType(components, element)
if useShortName {
if component != nil && !strings.Contains(str, component.ShortName) {
str = fmt.Sprintf("%s %s%s", str, component.ShortName, delimiter)
}
} else {
if component != nil && !strings.Contains(str, component.LongName) {
str = fmt.Sprintf("%s %s%s", str, component.LongName, delimiter)
}

continue
}

if component != nil && !strings.Contains(str, component.LongName) {
str = fmt.Sprintf("%s %s%s", str, component.LongName, delimiter)
}
}

if str == "" {
return nil
}

str = strings.TrimPrefix(strings.TrimSuffix(str, delimiter), " ")

return &str
}
Loading

0 comments on commit fa05eb5

Please sign in to comment.