process-locations.lisp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. (defun ts->ms (ts)
  2. (+ (* 1000 (local-time:timestamp-to-unix ts))
  3. (floor (local-time:nsec-of ts) 1000000)))
  4. (defun ms->ts (ms)
  5. (multiple-value-bind (unix msec) (floor ms 1000)
  6. (local-time:unix-to-timestamp unix :nsec (* msec 1000000))))
  7. (defun $between (a from to)
  8. (kv ($>= a from) ($< a to)))
  9. (defun reverse-geocode (lat lon)
  10. (let* ((data (yason:parse
  11. (flexi-streams:octets-to-string
  12. (drakma:http-request
  13. (format nil
  14. "http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&lat=~A&lon=~A"
  15. lat lon)))))
  16. (address (gethash "address" data)))
  17. (values (gethash "display_name" data)
  18. address)))
  19. (defmethod cl-mongo::bson-encode((key string) (value local-time:timestamp) &key (array nil))
  20. (cl-mongo::bson-encode key (cl-mongo::make-bson-time (ts->ms value)) :array array))
  21. (defun make-place-doc (loc)
  22. (let* ((point (cdr loc))
  23. (lat (geo:latitude-deg point))
  24. (lon (geo:longitude-deg point)))
  25. (multiple-value-bind (text address) (reverse-geocode lat lon)
  26. (kv
  27. (kv :ts (car loc))
  28. (kv :type "place")
  29. (kv :title text)
  30. (kv :language (gethash "country_code" address))
  31. (kv :loc (kv (kv :type "Point") ; GeoJSON Point
  32. (kv :coordinates (list lon lat))))))))
  33. (defun save-places (places)
  34. (loop for place in places
  35. do (db.insert "events" (make-place-doc place))))
  36. (defun extract-places (locs)
  37. (loop
  38. for loc in locs
  39. for point = (cdr loc)
  40. with last-place and last-loc and moved and need-add
  41. when last-loc do (setf moved
  42. (geo:distance>=
  43. (geo:distance-between
  44. point
  45. (cdr last-loc))
  46. (geo:distance-meters 100)))
  47. when moved do (setf last-loc loc ; Store loc as possible place
  48. need-add t)
  49. when (and need-add last-place last-loc
  50. (geo:distance<
  51. (geo:distance-between
  52. (cdr last-place)
  53. (cdr last-loc))
  54. (geo:distance-meters 200))) do (setf need-add nil) ; Do not add same place after glitch
  55. when (and need-add
  56. (> (local-time:timestamp-difference
  57. (car loc)
  58. (car last-loc))
  59. 400)) ; When stayed in 100m for 6.6mins
  60. collect last-loc
  61. and do (setf need-add nil) (setf last-place last-loc)
  62. when (not last-loc) do (setf last-loc loc)))
  63. (defun doc->point (doc)
  64. (let ((coord (get-element "coordinates"
  65. (get-element "loc" doc))))
  66. (geo:point-deg (second coord) (first coord))))
  67. (defun doc->ts (doc)
  68. (local-time:unix-to-timestamp
  69. (floor (get-element "timestampMs" doc) 1000)))
  70. (defun docs->locs (docs)
  71. (loop for doc in docs
  72. collect (cons (doc->ts doc) (doc->point doc))))
  73. (defun ts->kv (field ts)
  74. (let ((nd (local-time:timestamp+ ts 1 :day)))
  75. ($between field
  76. (* 1000 (local-time:timestamp-to-unix ts))
  77. (* 1000 (local-time:timestamp-to-unix nd)))))
  78. (defun takedown-import (from-date)
  79. (let ((now (local-time:now)))
  80. (loop
  81. for ts = (local-time:parse-timestring from-date) then (local-time:timestamp+ ts 1 :day)
  82. for locs = (docs->locs (docs (db.sort "locations" (ts->kv "timestampMs" ts) :field "timestampMs" :limit 1440)))
  83. for places = (extract-places locs)
  84. when places do
  85. (format t "Inserting ~A places for ~A~%" (length places) ts)
  86. (save-places places)
  87. until (local-time:timestamp> ts now)
  88. finally (return ts))))
  89. (defvar *google-cookie* "__utma=237271068.717680786.1400099247.1400832512.1401060155.8; __utmb=237271068.1.10.1401060155; __utmc=237271068; __utmz=237271068.1400099247.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); PREF=ID=1286af472975083f:U=66bfdd2c2bd546ec:FF=0:LD=en:TM=1387331606:LM=1387331648:GM=1:S=HyJJz5GiPYDO3enC; S_awfe=Flyca3yNcq7bRAELW3sHjA; S=adwords-kwoptimization=b5C-NozKx2e3M_Ey4fAz8Q:adwords-usermgmt=aI9og5GCca-P2__xQn18JA:adwords-common-ui=8D_eUxAYAsq3Vb2nq2sHEA:awfe=9MHnmAxQpPW7cx1DI0UVyg:awfe-efe=9MHnmAxQpPW7cx1DI0UVyg:adwords-campaignmgmt=RLKckyB0orOMG3ZDyKgvvg:adwords-navi=mLrH6o9Yts0R11WpddXxLA:adwords-conversiontracking=W211LBaz36B5ZeTzW4DNAA:static_files=_yP-7t9tqQM:payments=_ni1ZumEGMN4fOzdKRqH7w; NID=67=rrSfF1FmREywf26qNS2Y9H8wqUm9Ld3EtkVd9wZl7anEjYJKijA_4xCWKiNNiT4Coqdg-yA-Rswh3LTzCj5Mj-oQbc_MFQc5LqYTMBtdxpYPmaJHKom8Zk60dI9vxnYm4nP3qHSc0m84blpORHhzDdCMF4I-_-x9X-53Y_Iw4DwkOmSdBz5QPih3YTzzsbJZURmWqjLmtqj6JEa-4ylUwWNWM4puhCJEG69zjF4BjAfUuK_y1A3SEg3ptmGrttEZOvOVBZDGFMIJ0l85aa7Chud687F5DyAgi1qGOs946JD22CSU39PKyg; HSID=A7iaFUZxprrjrPKeS; SSID=AvKqEId4AsgbqLtOj; APISID=JFjZBUABTu9DaM0C/AYUyz9k8ypD0yblUO; SAPISID=QDr6_QSyPK0KKySp/AYUaTYz2U06LZYAcM; OGPC=4061130-2:; SID=DQAAAPoCAAAR29_N5Y6ftVeVr8w7iuna8fqkpEgI6D-9SLe26d835wthWtZsL5sL-aUWi6J4-6GEybslhCf4aJCxnBV4xIkKKiRDEhtqQAztV-kr1lbYQ6-FewxvQB63_eqlIqobVo_k-bQAirTpDBPsMwrY27PuM4qPJDQI8i9TYnU7F5bEph0DF-h7bJ9cbEQewoZRlyjopHClpZk3Z9Bh8i8vBbFZfDkTRXXD59vRO6BQrqcGvXdrzRqV8fzCDEB3EM7xd75y2y2l3hNXhQSZiAGQaZhrdxQ461BQ1i7Kug2vfKv1VSW5pCOratacplqSjsEXhj9hIsrT_gkiW2xWwLLrEBTF3vcM1tzWK7vinjm78ZPJwq9j3ZzDJ07svQ68ssTOpxQ027e-frCevBHvXFBtY8MubNtb-IpzOjEeM7DmCRP91z0ia5zQU5V7CHb78_cYbL_6gj0_OVc-kXoq-CLGS6NiQMTFHxf6uaE4Qu4aXNndqU45s83zw028y0SSHwv7l5OeiGYUoCPUfd8fazfPnV8-zia4BYuuZF4vn9s0MzsdkNmf45RSSXzFlAE97fzgtzzfcPtSqi-eTFkF9R_UEER8ZDqXL6B-9AD2rosoEZebeFD-YzCgP31GfzbvuTYjkPHKsRxD9Lbd3Kt7UxaS-bLwPNycqABwxemkIANKsbDDwwmLw4FxmC5uPXxrbx6bfG9ZzK-qxZIS3I3c_S1yRtsPuFCiCwKLcSGnIiK50IBL8Rd8iuKrWEYkRadwFgUn8JcgsnGKVfmQ8PdYcj6AIm5RZUr54fjbwgar964otDgQlKUl75GxZYzQwHf22zfZqHeNrbvz6J4IPS2iMh1BNrBxpAEA6uouJsQPa-77ETRJpHuF0GpBS14PSE-oI8jUS0i787JCnZBS7PsBHGQi2NMCDY_NRQybVeazdrEkPVYecZOfwGB7WDb6asHB-wRUhWHUNivX9DGEe9tuKV0JYY5PS93PdboxJYY7O7j9XlLLOU97YOI2Rm4hxGm0M8kUKIw")
  90. (defvar *google-manual-header* "fqHTCQQE3WGc62jBT3NPR9y3BmI:1401060149338")
  91. (defun load-history (from &optional (to (local-time:timestamp+ from 1 :day)) (trim nil))
  92. (loop for loc in (cadadr (yason:parse
  93. (flexi-streams:octets-to-string
  94. (drakma:http-request
  95. "https://maps.google.com/locationhistory/b/0/apps/pvjson?t=0"
  96. :method :post
  97. :user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36"
  98. :content (format nil "[null,~A,~A,~A]"
  99. (ts->ms from)
  100. (ts->ms to)
  101. (if trim "true" "false"))
  102. :additional-headers (list (cons "x-manualheader" *google-manual-header*)
  103. (cons "cookie" *google-cookie*)
  104. '("referer" . "https://maps.google.com/locationhistory/b/0"))))))
  105. collect (cons (ms->ts (parse-integer (cadr loc)))
  106. (geo:point-deg (caddr loc) (cadddr loc)))))
  107. (defun point->doc (point)
  108. (when point
  109. (kv (kv :type "Point") ; GeoJSON Point
  110. (kv :coordinates (list (geo:longitude-deg point)
  111. (geo:latitude-deg point))))))
  112. (defun make-location-doc (loc)
  113. (kv
  114. (kv "timestampMs" (ts->ms (car loc)))
  115. (kv "loc" (point->doc (cdr loc)))))
  116. (defun save-locations (locations)
  117. (loop for loc in locations
  118. do (db.insert "locations" (make-location-doc loc))))
  119. (defun json-import (from &optional (to (local-time:timestamp+ from 1 :day)))
  120. (let* ((locs (load-history from to nil))
  121. (places (extract-places locs)))
  122. (format t "Saving ~A locations for ~A~%" (length locs) from)
  123. (save-locations locs)
  124. (format t "Saving ~A place events for ~A~%" (length places) from)
  125. (save-places places)))
  126. (defun find-location-at (ms)
  127. (let* ((ts (ms->ts ms))
  128. (docs (docs (db.sort "locations"
  129. ($between
  130. "timestampMs"
  131. (ts->ms (local-time:timestamp- ts 5 :minute))
  132. (ts->ms (local-time:timestamp+ ts 5 :minute)))
  133. :field "timestampMs" :limit 15)))
  134. less
  135. greater)
  136. (loop
  137. for doc in docs
  138. for docMs = (get-element "timestampMs" doc)
  139. when (<= docMs ms) do (setf less doc)
  140. when (and (not greater) (>= docMs ms)) do (setf greater doc))
  141. (when (and less greater)
  142. (geo:interpolate-between-points
  143. (doc->point less)
  144. (doc->point greater)
  145. (/ (- ms (get-element "timestampMs" less))
  146. (- (get-element "timestampMs" greater)
  147. (get-element "timestampMs" less)))))))