diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..b25f4f9 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,30 @@ +kind: pipeline +type: kubernetes +name: default + +steps: +- name: test + image: golang:1.18 + volumes: + - name: deps + path: /go + commands: + - go test -v + - go build + +- name: docker + image: harbor.ntppool.org/ntppool/drone-kaniko:main + pull: always + volumes: + - name: deps + path: /go + settings: + repo: library/locationcode + registry: harbor.ntppool.org + auto_tag: true + tags: SHA7,${DRONE_SOURCE_BRANCH} + cache: true + username: + from_secret: harbor_library_username + password: + from_secret: harbor_library_password diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb80074 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.18-alpine3.15 AS build +RUN apk --no-cache add git + +WORKDIR /go/src/ +ADD . /go/src/ +RUN go install -v ./... + + +FROM alpine:3.15 +USER root +RUN apk --no-cache add ca-certificates + +RUN addgroup lc && adduser -D -G lc lc +WORKDIR / +COPY --from=build /go/bin/locationcode /bin/ + +USER lc + +RUN mkdir /tmp/data + +CMD ["/bin/locationcode", "-data-dir=/tmp/data"] diff --git a/airports.go b/airports.go new file mode 100644 index 0000000..9772d98 --- /dev/null +++ b/airports.go @@ -0,0 +1,159 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "path" + "sort" + "strconv" + "strings" + + "github.com/golang/geo/s2" + alphafoxtrot "github.com/grumpypixel/go-airport-finder" + "github.com/labstack/echo/v4" +) + +type Airport struct { + Name string + Code string + Distance float64 + data *alphafoxtrot.Airport +} + +func main() { + + var dataDir = flag.String("data-dir", "./data", "Data cache directory") + + flag.Parse() + + validateData(*dataDir) + + finder := alphafoxtrot.NewAirportFinder() + + // LoadOptions come with preset filepaths + options := alphafoxtrot.PresetLoadOptions(*dataDir) + + // filter := alphafoxtrot.AirportTypeLarge | alphafoxtrot.AirportTypeMedium + filter := alphafoxtrot.AirportTypeRunways + + // Load the data into memory + if err := finder.Load(options, filter); len(err) > 0 { + log.Println("errors:", err) + } + + e := echo.New() + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusOK, "location code service") + }) + e.GET("/v1/code", func(c echo.Context) error { + + countryISOCode := c.QueryParam("cc") + countryISOCode = strings.ToUpper(countryISOCode) + + radiusString := c.QueryParam("radius") + + var radiusKM int + if len(radiusString) > 0 { + radiusKM, _ = strconv.Atoi(radiusString) + } + + if radiusKM < 10 { + radiusKM = 100 + } + + latitude, longitude, err := getLatLng(c) + if err != nil { + return c.String(http.StatusBadRequest, fmt.Sprintf("invalid lat or lng: %s", err)) + } + + ipLocation := s2.LatLngFromDegrees(latitude, longitude) + + maxResults := 100 + radiusInMeters := float64(radiusKM) * 1000 + airportsRaw := finder.FindNearestAirportsByCountry(countryISOCode, latitude, longitude, radiusInMeters, maxResults, filter) + + airports := []*alphafoxtrot.Airport{} + for _, ap := range airportsRaw { + if len(ap.IATACode) == 0 { + continue + } + airports = append(airports, ap) + } + + llCache := map[int]s2.LatLng{} + for i, ap := range airports { + ll := s2.LatLngFromDegrees(ap.LatitudeDeg, ap.LongitudeDeg) + llCache[i] = ll + } + + r := []*Airport{} + + for i, airport := range airports { + // fmt.Printf("%d %s: %+v\n", i, airport.Name, airport) + + code := strings.ToLower(airport.Country.ISOCode + airport.IATACode) + + distance := float64(ipLocation.Distance(llCache[i])) * 6371.01 + + a := &Airport{ + Name: airport.Name, + Code: code, + Distance: distance, + data: airport, + } + r = append(r, a) + } + + sort.Slice(r, func(i, j int) bool { + if r[i].data.Type == r[j].data.Type { + return r[i].Distance < r[j].Distance + } + return airports[i].Type < airports[j].Type + }) + + if len(r) > 10 { + r = r[0:10] + } + + fmt.Printf("got %d airports, filtered to %d, returning %d\n", len(airportsRaw), len(airports), len(r)) + + return c.JSON(http.StatusOK, r) + }) + + e.Logger.Fatal(e.Start(":8000")) + +} + +func validateData(dataDir string) { + downloadFiles := false + for _, filename := range alphafoxtrot.OurAirportsFiles { + filepath := path.Join(dataDir, filename) + if _, err := os.Stat(filepath); os.IsNotExist(err) { + downloadFiles = true + break + } + } + if downloadFiles { + fmt.Println("Downloading CSV files from OurAirports.com...") + alphafoxtrot.DownloadDatabase(dataDir) + } +} + +func getLatLng(c echo.Context) (float64, float64, error) { + latitudeStr := c.QueryParam("lat") // 37.3793 + longitudeStr := c.QueryParam("lng") // -122.12 + + latitude, err := strconv.ParseFloat(latitudeStr, 64) + if err != nil { + return 0, 0, err + } + longitude, err := strconv.ParseFloat(longitudeStr, 64) + if err != nil { + return 0, 0, err + } + + return latitude, longitude, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e6e3fa4 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module go.askask.com/locationcode + +go 1.18 + +require ( + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 + github.com/grumpypixel/go-airport-finder v0.0.0-20210902211810-793a4fb1490b + github.com/labstack/echo/v4 v4.7.2 +) + +require ( + github.com/grumpypixel/go-webget v0.0.1 // indirect + github.com/labstack/gommon v0.3.1 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect + golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect + golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a16c7e1 --- /dev/null +++ b/go.sum @@ -0,0 +1,44 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/grumpypixel/go-airport-finder v0.0.0-20210902211810-793a4fb1490b h1:CAT7j8NnDTijm+Mqc49PowzRwffiQwoNTdacBtAaPac= +github.com/grumpypixel/go-airport-finder v0.0.0-20210902211810-793a4fb1490b/go.mod h1:VSzLdQ8TugVDF/ZqngnVa/4budj2JLbC3spdz4eKEd4= +github.com/grumpypixel/go-webget v0.0.0-20210513194017-df576311f21d h1:ZD475Db8FZRfptdzpvWiAQPRkElVrITDmg7yiTN+vk0= +github.com/grumpypixel/go-webget v0.0.0-20210513194017-df576311f21d/go.mod h1:TZ3kzdKP644McT9EqfvX6vRcnoKxAGNJVbUGfDCgbpA= +github.com/grumpypixel/go-webget v0.0.1 h1:JByEaxRKMK3ACvARJCMva1Tb+mKmZPpIDF9gcgf42OU= +github.com/grumpypixel/go-webget v0.0.1/go.mod h1:TZ3kzdKP644McT9EqfvX6vRcnoKxAGNJVbUGfDCgbpA= +github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=