process-locations.lisp 2.9 KB

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