| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- (defun ts->ms (ts)
- (+ (* 1000 (local-time:timestamp-to-unix ts))
- (floor (local-time:nsec-of ts) 1000000)))
- (defun ms->ts (ms)
- (multiple-value-bind (unix msec) (floor ms 1000)
- (local-time:unix-to-timestamp unix :nsec (* msec 1000000))))
- (defun $between (a from to)
- (kv ($>= a from) ($< a to)))
- (defun reverse-geocode (lat lon)
- (let* ((data (yason:parse
- (flexi-streams:octets-to-string
- (drakma:http-request
- (format nil
- "http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&lat=~A&lon=~A"
- lat lon)))))
- (address (gethash "address" data)))
- (values (gethash "display_name" data)
- address)))
- (defmethod cl-mongo::bson-encode((key string) (value local-time:timestamp) &key (array nil))
- (cl-mongo::bson-encode key (cl-mongo::make-bson-time (ts->ms value)) :array array))
- (defun make-place-doc (loc)
- (let* ((point (cdr loc))
- (lat (geo:latitude-deg point))
- (lon (geo:longitude-deg point)))
- (multiple-value-bind (text address) (reverse-geocode lat lon)
- (kv
- (kv :ts (car loc))
- (kv :type "place")
- (kv :title text)
- (kv :language (gethash "country_code" address))
- (kv :loc (kv (kv :type "Point") ; GeoJSON Point
- (kv :coordinates (list lon lat))))))))
- (defun save-places (places)
- (loop for place in places
- do (db.insert "events" (make-place-doc place))))
- (defun extract-places (locs)
- (loop
- for loc in locs
- for point = (cdr loc)
- with last-place and last-loc and moved and need-add
- when last-loc do (setf moved
- (geo:distance>=
- (geo:distance-between
- point
- (cdr last-loc))
- (geo:distance-meters 100)))
- when moved do (setf last-loc loc ; Store loc as possible place
- need-add t)
- when (and need-add last-place last-loc
- (geo:distance<
- (geo:distance-between
- (cdr last-place)
- (cdr last-loc))
- (geo:distance-meters 200))) do (setf need-add nil) ; Do not add same place after glitch
- when (and need-add
- (> (local-time:timestamp-difference
- (car loc)
- (car last-loc))
- 400)) ; When stayed in 100m for 6.6mins
- collect last-loc
- and do (setf need-add nil) (setf last-place last-loc)
- when (not last-loc) do (setf last-loc loc)))
- (defun doc->point (doc)
- (let ((coord (get-element "coordinates"
- (get-element "loc" doc))))
- (geo:point-deg (second coord) (first coord))))
- (defun doc->ts (doc)
- (local-time:unix-to-timestamp
- (floor (get-element "timestampMs" doc) 1000)))
- (defun docs->locs (docs)
- (loop for doc in docs
- collect (cons (doc->ts doc) (doc->point doc))))
- (defun ts->kv (field ts)
- (let ((nd (local-time:timestamp+ ts 1 :day)))
- ($between field
- (* 1000 (local-time:timestamp-to-unix ts))
- (* 1000 (local-time:timestamp-to-unix nd)))))
- (defun takedown-import (from-date)
- (let ((now (local-time:now)))
- (loop
- for ts = (local-time:parse-timestring from-date) then (local-time:timestamp+ ts 1 :day)
- for locs = (docs->locs (docs (db.sort "locations" (ts->kv "timestampMs" ts) :field "timestampMs" :limit 1440)))
- for places = (extract-places locs)
- when places do
- (format t "Inserting ~A places for ~A~%" (length places) ts)
- (save-places places)
- until (local-time:timestamp> ts now)
- finally (return ts))))
- (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")
- (defvar *google-manual-header* "fqHTCQQE3WGc62jBT3NPR9y3BmI:1401060149338")
- (defun load-history (from &optional (to (local-time:timestamp+ from 1 :day)) (trim nil))
- (loop for loc in (cadadr (yason:parse
- (flexi-streams:octets-to-string
- (drakma:http-request
- "https://maps.google.com/locationhistory/b/0/apps/pvjson?t=0"
- :method :post
- :user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36"
- :content (format nil "[null,~A,~A,~A]"
- (ts->ms from)
- (ts->ms to)
- (if trim "true" "false"))
- :additional-headers (list (cons "x-manualheader" *google-manual-header*)
- (cons "cookie" *google-cookie*)
- '("referer" . "https://maps.google.com/locationhistory/b/0"))))))
- collect (cons (ms->ts (parse-integer (cadr loc)))
- (geo:point-deg (caddr loc) (cadddr loc)))))
- (defun point->doc (point)
- (when point
- (kv (kv :type "Point") ; GeoJSON Point
- (kv :coordinates (list (geo:longitude-deg point)
- (geo:latitude-deg point))))))
- (defun make-location-doc (loc)
- (kv
- (kv "timestampMs" (ts->ms (car loc)))
- (kv "loc" (point->doc (cdr loc)))))
- (defun save-locations (locations)
- (loop for loc in locations
- do (db.insert "locations" (make-location-doc loc))))
- (defun json-import (from &optional (to (local-time:timestamp+ from 1 :day)))
- (let* ((locs (load-history from to nil))
- (places (extract-places locs)))
- (format t "Saving ~A locations for ~A~%" (length locs) from)
- (save-locations locs)
- (format t "Saving ~A place events for ~A~%" (length places) from)
- (save-places places)))
- (defun find-location-at (ms)
- (let* ((ts (ms->ts ms))
- (docs (docs (db.sort "locations"
- ($between
- "timestampMs"
- (ts->ms (local-time:timestamp- ts 5 :minute))
- (ts->ms (local-time:timestamp+ ts 5 :minute)))
- :field "timestampMs" :limit 15)))
- less
- greater)
- (loop
- for doc in docs
- for docMs = (get-element "timestampMs" doc)
- when (<= docMs ms) do (setf less doc)
- when (and (not greater) (>= docMs ms)) do (setf greater doc))
- (when (and less greater)
- (geo:interpolate-between-points
- (doc->point less)
- (doc->point greater)
- (/ (- ms (get-element "timestampMs" less))
- (- (get-element "timestampMs" greater)
- (get-element "timestampMs" less)))))))
|