package nominatim import ( "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "reflect" "strconv" ) type Nominatim struct { siteName string } var ( ErrorMissingSiteName = errors.New("siteName must be defined") ) func New(siteName string) (*Nominatim, error) { if siteName == "" { return nil, ErrorMissingSiteName } return &Nominatim{ siteName: siteName, }, nil } // Search for coordinates based on q // API endpoint: https://nominatim.openstreetmap.org/search func (n *Nominatim) Search(q string) (*SearchResults, error) { url := fmt.Sprintf("https://nominatim.openstreetmap.org/search?q=%s&format=jsonv2", url.QueryEscape(q)) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", n.siteName) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s", resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } result := SearchResults{} err = json.Unmarshal(body, &result) if err != nil { return nil, err } return &result, nil } // Reverse search for location based on lat and lon // API endpoint: https://nominatim.openstreetmap.org/reverse func (n *Nominatim) Reverse(lat, lon float64) (*ReverseResult, error) { url := fmt.Sprintf("https://nominatim.openstreetmap.org/reverse?lat=%.16f&lon=%.16f&format=jsonv2", lat, lon) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", n.siteName) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s", resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } result := ReverseResult{} err = json.Unmarshal(body, &result) if err != nil { return nil, err } if allFieldsNil(result) { return nil, fmt.Errorf("no result") } return &result, nil } type LookupResult struct { Coordinates Location string `json:"location"` } // Lookup location coordinates based on q func (n *Nominatim) Lookup(q string) (*LookupResult, error) { r, err := n.Search(q) if err != nil { return nil, err } if len(*r) == 0 { return nil, fmt.Errorf("no result") } firstResult := (*r)[0] lon, err := strconv.ParseFloat(*firstResult.Longitude, 64) if err != nil { return nil, err } lat, err := strconv.ParseFloat(*firstResult.Latitude, 64) if err != nil { return nil, err } return &LookupResult{ Location: *firstResult.Name, Coordinates: Coordinates{ Latitude: lat, Longitude: lon, }, }, nil } // Lookup location name based on lat and lon func (n *Nominatim) ReverseLookup(lat, lon float64) (*LookupResult, error) { r, err := n.Reverse(lat, lon) if err != nil { return nil, err } return &LookupResult{ Location: *r.Name, Coordinates: Coordinates{ Latitude: lat, Longitude: lon, }, }, nil } // Check if all fields on s is nil func allFieldsNil(s interface{}) bool { v := reflect.ValueOf(s) // Check if the passed value is a struct if v.Kind() != reflect.Struct { return false } // Iterate through all fields in the struct for i := 0; i < v.NumField(); i++ { field := v.Field(i) // Check if the field is a pointer and is nil if field.Kind() == reflect.Ptr && !field.IsNil() { return false } } return true }