process-locations.lisp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. (defun make-place-doc (loc)
  20. (let* ((point (cdr loc))
  21. (lat (geo:latitude-deg point))
  22. (lon (geo:longitude-deg point)))
  23. (multiple-value-bind (text address) (reverse-geocode lat lon)
  24. (kv
  25. (kv :ts (ts->ms (car loc)))
  26. (kv :type "place")
  27. (kv :title text)
  28. (kv :language (gethash "country_code" address))
  29. (kv :loc (kv (kv :type "Point") ; GeoJSON Point
  30. (kv :coordinates (list lon lat))))))))
  31. (defun save-places (places)
  32. (loop for place in places
  33. do (db.insert "events" (make-place-doc place))))
  34. (defun extract-places (locs)
  35. (loop
  36. for loc in locs
  37. for point = (cdr loc)
  38. with last-place and last-loc and moved and need-add
  39. when last-loc do (setf moved
  40. (geo:distance>=
  41. (geo:distance-between
  42. point
  43. (cdr last-loc))
  44. (geo:distance-meters 100)))
  45. when moved do (setf last-loc loc ; Store loc as possible place
  46. need-add t)
  47. when (and need-add last-place last-loc
  48. (geo:distance<
  49. (geo:distance-between
  50. (cdr last-place)
  51. (cdr last-loc))
  52. (geo:distance-meters 200))) do (setf need-add nil) ; Do not add same place after glitch
  53. when (and need-add
  54. (> (local-time:timestamp-difference
  55. (car loc)
  56. (car last-loc))
  57. 400)) ; When stayed in 100m for 6.6mins
  58. collect last-loc
  59. and do (setf need-add nil) (setf last-place last-loc)
  60. when (not last-loc) do (setf last-loc loc)))
  61. (defun doc->point (doc)
  62. (let ((coord (get-element "coordinates"
  63. (get-element "loc" doc))))
  64. (geo:point-deg (second coord) (first coord))))
  65. (defun doc->ts (doc)
  66. (local-time:unix-to-timestamp
  67. (floor (get-element "timestampMs" doc) 1000)))
  68. (defun docs->locs (docs)
  69. (loop for doc in docs
  70. collect (cons (doc->ts doc) (doc->point doc))))
  71. (defun ts->kv (field ts)
  72. (let ((nd (local-time:timestamp+ ts 1 :day)))
  73. ($between field
  74. (* 1000 (local-time:timestamp-to-unix ts))
  75. (* 1000 (local-time:timestamp-to-unix nd)))))
  76. (defun takedown-import (from-date)
  77. (let ((now (local-time:now)))
  78. (loop
  79. for ts = (local-time:parse-timestring from-date) then (local-time:timestamp+ ts 1 :day)
  80. for locs = (docs->locs (docs (db.sort "locations" (ts->kv "timestampMs" ts) :field "timestampMs" :limit 1440)))
  81. for places = (extract-places locs)
  82. when places do
  83. (format t "Inserting ~A places for ~A~%" (length places) ts)
  84. (save-places places)
  85. until (local-time:timestamp> ts now)
  86. finally (return ts))))
  87. (defvar *google-cookie* "__utma=237271068.1241244678.1400710443.1400710443.1400710443.1; __utmb=237271068.1.10.1400710443; __utmc=237271068; __utmz=237271068.1400710443.1.1.utmcsr=accounts.google.com|utmccn=(referral)|utmcmd=referral|utmcct=/CheckCookie; HSID=AHszUCr82JTQW17_e; SSID=ApnZu-rKMHkdGKhs8; APISID=UuaDeGjPNgssr6MD/ADVZqAW8jHCM6k3hB; SAPISID=G3CQ4IwqoD0VeqwU/AvbYPYWQ4j7TfA-Me; SID=DQAAAOoAAABmxJYlkDzluCCTEiRrYpicGjIpxCcFVOp8teXB5_LK__YNh1ic_r3o73qT6XTw6dtyGBMU6f6S2xEWUTzQ3WyGTHQDU9iA4-JDWKOEKL30gbJe--j7o6JcVRMHDtusEVnIXGR5lfWZbqJiQ1rcHEyfOdwa_YFHDNGxXZX8CqxOb2XbJg4LsCV2QAkAgPtnttzJy5L39JvVngkoTIf78_CAyVqZb-UPmWAHAMedw3pMjZNuK-vmEpABNiGyDPf6jXn8rSLFMd4VOxhruIsWttSxjS6w2docgpgaL2SDD8HLGeUuxqK7Ww4c_WLhkOiWF1g; NID=67=i0y21lGpfKdTt8LRVZKl-mUp54GsSFLb8FGLVzLFiU1gG4KbQ52HNNPf3-HrXhvmN81AxzGwQPPOIRQ6Z8ciMsG-_Ev7mbeoDtfw8gzLnCiaReU8vVayS2NSFQi6_zg5pStsIlG91eRyYjgy3CxpU-vnO_qYVTs4i03j3XQ")
  88. (defvar *google-manual-header* "GUem3al6owB4SvGgikUOjC6MFOw:1400710441096")
  89. (defun load-history (from &optional (to (local-time:timestamp+ from 1 :day)) (trim nil))
  90. (loop for loc in (cadadr (yason:parse
  91. (flexi-streams:octets-to-string
  92. (drakma:http-request
  93. "https://maps.google.com/locationhistory/b/0/apps/pvjson?t=0"
  94. :method :post
  95. :user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36"
  96. :content (format nil "[null,~A,~A,~A]"
  97. (ts->ms from)
  98. (ts->ms to)
  99. (if trim "true" "false"))
  100. :additional-headers (list (cons "x-manualheader" *google-manual-header*)
  101. (cons "cookie" *google-cookie*)
  102. '("referer" . "https://maps.google.com/locationhistory/b/0"))))))
  103. collect (cons (ms->ts (parse-integer (cadr loc)))
  104. (geo:point-deg (caddr loc) (cadddr loc)))))
  105. (defun point->doc (point)
  106. (when point
  107. (kv (kv :type "Point") ; GeoJSON Point
  108. (kv :coordinates (list (geo:longitude-deg point)
  109. (geo:latitude-deg point))))))
  110. (defun make-location-doc (loc)
  111. (kv
  112. (kv "timestampMs" (ts->ms (car loc)))
  113. (kv "loc" (point->doc (cdr loc)))))
  114. (defun save-locations (locations)
  115. (loop for loc in locations
  116. do (db.insert "locations" (make-location-doc loc))))
  117. (defun json-import (from &optional (to (local-time:timestamp+ from 1 :day)))
  118. (let* ((locs (load-history from to nil))
  119. (places (extract-places locs)))
  120. (format t "Saving ~A locations for ~A~%" (length locs) from)
  121. (save-locations locs)
  122. (format t "Saving ~A place events for ~A~%" (length places) from)
  123. (save-places places)))
  124. (defun find-location-at (ms)
  125. (let* ((ts (ms->ts ms))
  126. (docs (docs (db.sort "locations"
  127. ($between
  128. "timestampMs"
  129. (ts->ms (local-time:timestamp- ts 5 :minute))
  130. (ts->ms (local-time:timestamp+ ts 5 :minute)))
  131. :field "timestampMs" :limit 15)))
  132. less
  133. greater)
  134. (loop
  135. for doc in docs
  136. for docMs = (get-element "timestampMs" doc)
  137. when (<= docMs ms) do (setf less doc)
  138. when (and (not greater) (>= docMs ms)) do (setf greater doc))
  139. (when (and less greater)
  140. (geo:interpolate-between-points
  141. (doc->point less)
  142. (doc->point greater)
  143. (/ (- ms (get-element "timestampMs" less))
  144. (- (get-element "timestampMs" greater)
  145. (get-element "timestampMs" less)))))))