geocode.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. package handlers
  2. import (
  3. "encoding/json"
  4. "io"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. "time"
  9. )
  10. const nominatimURL = "https://nominatim.openstreetmap.org/reverse"
  11. var geocodeClient = &http.Client{Timeout: 5 * time.Second}
  12. type nominatimResponse struct {
  13. DisplayName string `json:"display_name"`
  14. }
  15. func GeocodeReverse(w http.ResponseWriter, r *http.Request) {
  16. lat := r.URL.Query().Get("lat")
  17. lon := r.URL.Query().Get("lon")
  18. if lat == "" || lon == "" {
  19. writeError(w, http.StatusBadRequest, "lat and lon are required", nil)
  20. return
  21. }
  22. u, _ := url.Parse(nominatimURL)
  23. q := u.Query()
  24. q.Set("lat", lat)
  25. q.Set("lon", lon)
  26. q.Set("format", "json")
  27. q.Set("accept-language", "ru")
  28. u.RawQuery = q.Encode()
  29. req, _ := http.NewRequestWithContext(r.Context(), http.MethodGet, u.String(), nil)
  30. req.Header.Set("User-Agent", "Photoplaces/1.0")
  31. resp, err := geocodeClient.Do(req)
  32. if err != nil {
  33. writeError(w, http.StatusBadGateway, "geocoding service unavailable", err)
  34. return
  35. }
  36. defer resp.Body.Close()
  37. if resp.StatusCode != http.StatusOK {
  38. body, _ := io.ReadAll(resp.Body)
  39. writeJSON(w, http.StatusOK, map[string]interface{}{
  40. "display_name": nil,
  41. "raw_error": string(body),
  42. })
  43. return
  44. }
  45. var data nominatimResponse
  46. if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
  47. writeError(w, http.StatusInternalServerError, "failed to parse geocoding response", err)
  48. return
  49. }
  50. writeJSON(w, http.StatusOK, map[string]interface{}{
  51. "display_name": reverseAddress(data.DisplayName),
  52. })
  53. }
  54. // reverseAddress разворачивает адрес Nominatim (сперва самое точное -> в конце самое общее)
  55. // в человекочитаемый формат: страна, регион, город, улица, дом.
  56. func reverseAddress(addr string) string {
  57. if addr == "" {
  58. return ""
  59. }
  60. parts := strings.Split(addr, ", ")
  61. for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
  62. parts[i], parts[j] = parts[j], parts[i]
  63. }
  64. return strings.Join(parts, ", ")
  65. }
  66. }