2019-08-01 11:34:03 -08:00
|
|
|
package geonames
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2019-08-17 11:15:45 -08:00
|
|
|
"crypto/md5"
|
2019-08-01 11:34:03 -08:00
|
|
|
"encoding/csv"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2019-08-15 14:27:05 +07:00
|
|
|
"net/http"
|
2019-08-17 11:15:45 -08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-08-01 11:34:03 -08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-08-15 14:27:05 +07:00
|
|
|
"time"
|
2019-08-01 11:34:03 -08:00
|
|
|
|
2019-08-12 10:26:00 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
2019-08-17 11:03:48 +07:00
|
|
|
|
2019-08-01 11:34:03 -08:00
|
|
|
"github.com/huandu/go-sqlbuilder"
|
2019-08-17 11:03:48 +07:00
|
|
|
// "github.com/jmoiron/sqlx"
|
2019-08-01 11:34:03 -08:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sethgrid/pester"
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// The database table for Geoname
|
|
|
|
geonamesTableName = "geonames"
|
|
|
|
)
|
|
|
|
|
2019-08-12 10:26:00 -08:00
|
|
|
// List of country codes that will geonames will be downloaded for.
|
|
|
|
func ValidGeonameCountries(ctx context.Context) []string {
|
|
|
|
if webcontext.ContextEnv(ctx) == webcontext.Env_Dev {
|
|
|
|
return []string{"US"}
|
|
|
|
}
|
|
|
|
return []string{
|
2019-08-01 13:45:38 -08:00
|
|
|
"AD", "AR", "AS", "AT", "AU", "AX", "BD", "BE", "BG", "BM",
|
|
|
|
"BR", "BY", "CA", "CH", "CO", "CR", "CZ", "DE", "DK", "DO",
|
|
|
|
"DZ", "ES", "FI", "FO", "FR", "GB", "GF", "GG", "GL", "GP",
|
|
|
|
"GT", "GU", "HR", "HU", "IE", "IM", "IN", "IS", "IT", "JE",
|
|
|
|
"JP", "LI", "LK", "LT", "LU", "LV", "MC", "MD", "MH", "MK",
|
|
|
|
"MP", "MQ", "MT", "MX", "MY", "NC", "NL", "NO", "NZ", "PH",
|
|
|
|
"PK", "PL", "PM", "PR", "PT", "RE", "RO", "RU", "SE", "SI",
|
|
|
|
"SJ", "SK", "SM", "TH", "TR", "UA", "US", "UY", "VA", "VI",
|
|
|
|
"WF", "YT", "ZA"}
|
2019-08-12 10:26:00 -08:00
|
|
|
}
|
2019-08-01 11:34:03 -08:00
|
|
|
|
|
|
|
// FindGeonames ....
|
2019-08-17 11:03:48 +07:00
|
|
|
func (repo *Repository) FindGeonames(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Geoname, error) {
|
2019-08-01 11:34:03 -08:00
|
|
|
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonames")
|
|
|
|
defer span.Finish()
|
|
|
|
|
|
|
|
query := sqlbuilder.NewSelectBuilder()
|
|
|
|
query.Select("country_code,postal_code,place_name,state_name,state_code,county_name,county_code,community_name,community_code,latitude,longitude,accuracy")
|
|
|
|
query.From(geonamesTableName)
|
|
|
|
|
|
|
|
if orderBy == "" {
|
|
|
|
orderBy = "postal_code"
|
|
|
|
}
|
|
|
|
query.OrderBy(orderBy)
|
|
|
|
|
|
|
|
if where != "" {
|
|
|
|
query.Where(where)
|
|
|
|
}
|
|
|
|
|
|
|
|
queryStr, queryArgs := query.Build()
|
2019-08-17 11:03:48 +07:00
|
|
|
queryStr = repo.DbConn.Rebind(queryStr)
|
2019-08-01 11:34:03 -08:00
|
|
|
args = append(args, queryArgs...)
|
|
|
|
|
|
|
|
// fetch all places from the db
|
2019-08-17 11:03:48 +07:00
|
|
|
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrapf(err, "query - %s", query.String())
|
|
|
|
err = errors.WithMessage(err, "find regions failed")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over each row
|
|
|
|
resp := []*Geoname{}
|
|
|
|
for rows.Next() {
|
|
|
|
var (
|
|
|
|
v Geoname
|
|
|
|
err error
|
|
|
|
)
|
2019-08-01 13:45:38 -08:00
|
|
|
err = rows.Scan(&v.CountryCode, &v.PostalCode, &v.PlaceName, &v.StateName, &v.StateCode, &v.CountyName, &v.CountyCode, &v.CommunityName, &v.CommunityCode, &v.Latitude, &v.Longitude, &v.Accuracy)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
2019-08-01 13:45:38 -08:00
|
|
|
} else if v.PostalCode == "" {
|
2019-08-01 11:34:03 -08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
resp = append(resp, &v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindGeonamePostalCodes ....
|
2019-08-17 11:03:48 +07:00
|
|
|
func (repo *Repository) FindGeonamePostalCodes(ctx context.Context, where string, args ...interface{}) ([]string, error) {
|
2019-08-01 11:34:03 -08:00
|
|
|
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonamePostalCodes")
|
|
|
|
defer span.Finish()
|
|
|
|
|
|
|
|
query := sqlbuilder.NewSelectBuilder()
|
|
|
|
query.Select("postal_code")
|
|
|
|
query.From(geonamesTableName)
|
|
|
|
|
|
|
|
if where != "" {
|
|
|
|
query.Where(where)
|
|
|
|
}
|
|
|
|
|
|
|
|
queryStr, queryArgs := query.Build()
|
2019-08-17 11:03:48 +07:00
|
|
|
queryStr = repo.DbConn.Rebind(queryStr)
|
2019-08-01 11:34:03 -08:00
|
|
|
args = append(args, queryArgs...)
|
|
|
|
|
|
|
|
// fetch all places from the db
|
2019-08-17 11:03:48 +07:00
|
|
|
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrapf(err, "query - %s", query.String())
|
|
|
|
err = errors.WithMessage(err, "find regions failed")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over each row
|
|
|
|
resp := []string{}
|
|
|
|
for rows.Next() {
|
|
|
|
var (
|
|
|
|
v string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
err = rows.Scan(&v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
} else if v == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
resp = append(resp, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindGeonameRegions ....
|
2019-08-17 11:03:48 +07:00
|
|
|
func (repo *Repository) FindGeonameRegions(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Region, error) {
|
2019-08-01 11:34:03 -08:00
|
|
|
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonameRegions")
|
|
|
|
defer span.Finish()
|
|
|
|
|
|
|
|
query := sqlbuilder.NewSelectBuilder()
|
|
|
|
query.Select("distinct state_code", "state_name")
|
|
|
|
query.From(geonamesTableName)
|
|
|
|
|
|
|
|
if orderBy == "" {
|
|
|
|
orderBy = "state_name"
|
|
|
|
}
|
|
|
|
query.OrderBy(orderBy)
|
|
|
|
|
|
|
|
if where != "" {
|
|
|
|
query.Where(where)
|
|
|
|
}
|
|
|
|
|
|
|
|
queryStr, queryArgs := query.Build()
|
2019-08-17 11:03:48 +07:00
|
|
|
queryStr = repo.DbConn.Rebind(queryStr)
|
2019-08-01 11:34:03 -08:00
|
|
|
args = append(args, queryArgs...)
|
|
|
|
|
|
|
|
// fetch all places from the db
|
2019-08-17 11:03:48 +07:00
|
|
|
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrapf(err, "query - %s", query.String())
|
|
|
|
err = errors.WithMessage(err, "find regions failed")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over each row
|
|
|
|
resp := []*Region{}
|
|
|
|
for rows.Next() {
|
|
|
|
var (
|
|
|
|
v Region
|
|
|
|
err error
|
|
|
|
)
|
2019-08-01 13:45:38 -08:00
|
|
|
err = rows.Scan(&v.Code, &v.Name)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrapf(err, "query - %s", query.String())
|
|
|
|
return nil, err
|
2019-08-01 13:45:38 -08:00
|
|
|
} else if v.Code == "" || v.Name == "" {
|
2019-08-01 11:34:03 -08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
resp = append(resp, &v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
// GetGeonameCountry downloads geoname data for the country.
|
|
|
|
// Parses data and returns slice of Geoname
|
2019-08-17 11:29:50 -08:00
|
|
|
func (repo *Repository) GetGeonameCountry(ctx context.Context, country string) ([]Geoname, error) {
|
2019-08-17 11:15:45 -08:00
|
|
|
res := make([]Geoname, 0)
|
|
|
|
var err error
|
|
|
|
var resp *http.Response
|
2019-08-01 11:34:03 -08:00
|
|
|
|
|
|
|
u := fmt.Sprintf("http://download.geonames.org/export/zip/%s.zip", country)
|
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
h := fmt.Sprintf("%x", md5.Sum([]byte(u)))
|
|
|
|
cp := filepath.Join(os.TempDir(), h+".zip")
|
2019-08-01 11:34:03 -08:00
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
if _, err := os.Stat(cp); err != nil {
|
|
|
|
resp, err = pester.Get(u)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
2019-08-17 11:15:45 -08:00
|
|
|
// Add re-try three times after failing first time
|
|
|
|
// This reduces the risk when network is lagy, we still have chance to re-try.
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
resp, err = pester.Get(u)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second * 1)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
if err != nil {
|
2019-08-17 11:15:45 -08:00
|
|
|
err = errors.WithMessagef(err, "Failed to read countries from '%s'", u)
|
|
|
|
return res, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
2019-08-17 11:15:45 -08:00
|
|
|
defer resp.Body.Close()
|
2019-08-01 11:34:03 -08:00
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
// Create the file
|
|
|
|
out, err := os.Create(cp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
2019-08-17 11:15:45 -08:00
|
|
|
defer out.Close()
|
2019-08-15 14:27:05 +07:00
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
// Write the body to file
|
|
|
|
_, err = io.Copy(out, resp.Body)
|
2019-08-15 14:27:05 +07:00
|
|
|
if err != nil {
|
2019-08-17 11:15:45 -08:00
|
|
|
return nil, err
|
2019-08-15 14:27:05 +07:00
|
|
|
}
|
2019-08-17 11:15:45 -08:00
|
|
|
|
|
|
|
out.Close()
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
|
2019-08-17 11:15:45 -08:00
|
|
|
f, err := os.Open(cp)
|
2019-08-01 11:34:03 -08:00
|
|
|
if err != nil {
|
2019-08-17 11:15:45 -08:00
|
|
|
return nil, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
2019-08-17 11:15:45 -08:00
|
|
|
defer f.Close()
|
|
|
|
br := bufio.NewReader(f)
|
2019-08-01 11:34:03 -08:00
|
|
|
|
|
|
|
buff := bytes.NewBuffer([]byte{})
|
|
|
|
size, err := io.Copy(buff, br)
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
|
|
|
return res, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
b := bytes.NewReader(buff.Bytes())
|
|
|
|
zr, err := zip.NewReader(b, size)
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
|
|
|
return res, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range zr.File {
|
|
|
|
if f.Name == "readme.txt" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
fh, err := f.Open()
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
|
|
|
return res, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(fh)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
|
|
|
|
if strings.Contains(line, "\"") {
|
|
|
|
line = strings.Replace(line, "\"", "\\\"", -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
r := csv.NewReader(strings.NewReader(line))
|
|
|
|
r.Comma = '\t' // Use tab-delimited instead of comma <---- here!
|
|
|
|
r.LazyQuotes = true
|
|
|
|
r.FieldsPerRecord = -1
|
|
|
|
|
|
|
|
lines, err := r.ReadAll()
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
2019-08-01 11:34:03 -08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range lines {
|
|
|
|
|
|
|
|
gn := Geoname{
|
2019-08-01 13:45:38 -08:00
|
|
|
CountryCode: row[0],
|
|
|
|
PostalCode: row[1],
|
|
|
|
PlaceName: row[2],
|
|
|
|
StateName: row[3],
|
|
|
|
StateCode: row[4],
|
|
|
|
CountyName: row[5],
|
|
|
|
CountyCode: row[6],
|
2019-08-01 11:34:03 -08:00
|
|
|
CommunityName: row[7],
|
|
|
|
CommunityCode: row[8],
|
|
|
|
}
|
|
|
|
if row[9] != "" {
|
|
|
|
gn.Latitude, err = decimal.NewFromString(row[9])
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if row[10] != "" {
|
|
|
|
gn.Longitude, err = decimal.NewFromString(row[10])
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if row[11] != "" {
|
|
|
|
gn.Accuracy, err = strconv.Atoi(row[11])
|
|
|
|
if err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-15 14:27:05 +07:00
|
|
|
res = append(res, gn)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
2019-08-15 14:27:05 +07:00
|
|
|
err = errors.WithStack(err)
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
2019-08-15 14:27:05 +07:00
|
|
|
|
|
|
|
return res, err
|
2019-08-01 11:34:03 -08:00
|
|
|
}
|