process-locations.lisp 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. (defun ts->ms (ts)
  2. (+ (* 1000 (local-time:timestamp-to-unix ts))
  3. (floor (local-time:nsec-of ts) 1000000)))
  4. (defun doc->point (doc)
  5. (geo:point-deg
  6. (/ (get-element "latitudeE7" doc) 1d7)
  7. (/ (get-element "longitudeE7" doc) 1d7)))
  8. (defun doc->ts (doc)
  9. (local-time:unix-to-timestamp
  10. (floor (get-element "timestampMs" doc) 1000)))
  11. (defun day-kv (date)
  12. (ts->kv (local-time:parse-timestring date))
  13. (defun ts->kv (ts)
  14. (let ((nd (local-time:timestamp+ ts 1 :day)))
  15. ($between "timestampMs"
  16. (* 1000 (local-time:timestamp-to-unix ts))
  17. (* 1000 (local-time:timestamp-to-unix nd)))))
  18. (defun reverse-geocode (lat lon)
  19. (let* ((data (yason:parse
  20. (drakma:http-request
  21. (format nil
  22. "http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&lat=~A&lon=~A"
  23. lat lon)
  24. :want-stream t)))
  25. (address (gethash "address" data)))
  26. (values (gethash "display_name" data)
  27. address)))
  28. (defun make-place-doc (ts point)
  29. (let ((lat (geo:latitude-deg point))
  30. (lon (geo:longitude-deg point)))
  31. (multiple-value-bind (text address) (reverse-geocode lat lon)
  32. (kv
  33. (kv :ts (ts->ms ts))
  34. (kv :type "place")
  35. (kv :title text)
  36. (kv :language (gethash "country_code" address))
  37. (kv :loc (kv (kv :type "Point") ; GeoJSON Point
  38. (kv :coordinates (list lon lat))))))))
  39. (defun extract-places (docs)
  40. (loop
  41. for doc in docs
  42. for point = (doc->point doc)
  43. with last-place and moved and need-add
  44. when last-place do (setf moved
  45. (geo:distance>=
  46. (geo:distance-between
  47. point
  48. (doc->point last-place))
  49. (geo:distance-meters 200)))
  50. when moved do (setf last-place doc
  51. need-add t)
  52. when (and need-add
  53. (> (local-time:timestamp-difference
  54. (doc->ts doc)
  55. (doc->ts last-place))
  56. 400))
  57. collect (cons (doc->ts last-place) (doc->point last-place))
  58. and do (setf need-add nil)
  59. when (not last-place) do (setf last-place doc)))
  60. (defun takedown-import (from-date)
  61. (let ((now (local-time:now)))
  62. (loop
  63. for ts = (local-time:parse-timestring from-date) then (local-time:timestamp+ ts 1 :day)
  64. for locs = (docs (db.sort "locations" (ts->kv ts) :field "timestampMs" :limit 1440))
  65. for places = (extract-places locs)
  66. when places do
  67. (format t "Inserting ~A places for ~A~%" (length places) ts)
  68. (loop
  69. for place in places
  70. do (db.insert "events" (make-place-doc (car place) (cdr place))))
  71. until (local-time:timestamp> ts now)
  72. finally (return ts))))