Войти Регистрация
Код-ревью

35. Поиск мест рядом в Яндекс Картах

Сложная

Go · 62 строк

По координатам пользователя и радиусу в метрах обработчик ищет организации нужной категории и отдаёт их от ближней к дальней. Сначала базе отдаётся прямоугольник вокруг точки, затем расстояние уточняется формулой гаверсинуса.

В таблице places 2,4 млн организаций по России, индекс по (category, lat, lon). Приложение шлёт радиус 1 000 м, но параметр открыт в публичном API. Отметьте строки с ошибками.

В работе: Запрос «аптеки рядом» в поиске Карт: ответ нужен за 100 мс.

Как проверяем Нажмите на строку с ошибкой и коротко напишите, что не так. Баг засчитан, если отмечена хотя бы одна его строка. Зачёт — найдено не меньше 60 % багов с учётом серьёзности (критичный весит 3, обычный 2, мелкий 1), а лишних отметок не больше, чем багов в коде. Пустые строки, скобки и комментарии лишними не считаем. Ответ один на подход: после него показываем разбор.
геопоискгаверсинусbounding boxSQL-инъекцияdatabase/sql

Источник: наша задача на данных Яндекс Карт: поиск организаций рядом. Условие пересказано своими словами.

  1. package geosearch
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "fmt"
  6. "math"
  7. "net/http"
  8. "sort"
  9. "strconv"
  10. )
  11. type Place struct {
  12. ID int64 `json:"id"`
  13. Name string `json:"name"`
  14. Lat float64 `json:"lat"`
  15. Lon float64 `json:"lon"`
  16. Distance float64 `json:"distance_m"`
  17. }
  18. const earthRadiusM = 6371000.0
  19. func haversine(lat1, lon1, lat2, lon2 float64) float64 {
  20. dLat := lat2 - lat1
  21. dLon := lon2 - lon1
  22. a := math.Sin(dLat/2)*math.Sin(dLat/2) +
  23. math.Cos(lat1)*math.Cos(lat2)*math.Sin(dLon/2)*math.Sin(dLon/2)
  24. return 2 * earthRadiusM * math.Asin(math.Sqrt(a))
  25. }
  26. func Nearby(db *sql.DB) http.HandlerFunc {
  27. return func(w http.ResponseWriter, r *http.Request) {
  28. q := r.URL.Query()
  29. lat, _ := strconv.ParseFloat(q.Get("lat"), 64)
  30. lon, _ := strconv.ParseFloat(q.Get("lon"), 64)
  31. radius, _ := strconv.ParseFloat(q.Get("radius"), 64)
  32. category := q.Get("category")
  33. dLat := radius / 111000
  34. dLon := radius / 111000
  35. query := fmt.Sprintf("SELECT id, name, lat, lon FROM places WHERE category = '%s' "+
  36. "AND lat BETWEEN %f AND %f AND lon BETWEEN %f AND %f",
  37. category, lat-dLat, lat+dLat, lon-dLon, lon+dLon)
  38. rows, err := db.Query(query)
  39. if err != nil {
  40. http.Error(w, "db error", http.StatusInternalServerError)
  41. return
  42. }
  43. var result []Place
  44. for rows.Next() {
  45. var p Place
  46. if err := rows.Scan(&p.ID, &p.Name, &p.Lat, &p.Lon); err != nil {
  47. continue
  48. }
  49. p.Distance = haversine(lat, lon, p.Lat, p.Lon)
  50. result = append(result, p)
  51. }
  52. sort.Slice(result, func(i, j int) bool { return result[i].Distance < result[j].Distance })
  53. json.NewEncoder(w).Encode(result)
  54. }
  55. }