1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2010, 2011 Bert Burgemeister
4 ;;; This program is free software; you can redistribute it and/or modify
5 ;;; it under the terms of the GNU General Public License as published by
6 ;;; the Free Software Foundation; either version 2 of the License, or
7 ;;; (at your option) any later version.
9 ;;; This program is distributed in the hope that it will be useful,
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;; GNU General Public License for more details.
14 ;;; You should have received a copy of the GNU General Public License along
15 ;;; with this program; if not, write to the Free Software Foundation, Inc.,
16 ;;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 (setf *js-target-version
* 1.8)
22 (defparameter *t
* nil
)
23 (defparameter *tt
* nil
)
25 (cffi:define-foreign-library photogrammetrie
26 (:unix
(:or
"./libphotogrammetrie.so"
27 "../photogrammetrie/lib/libphotogrammetrie.so"))
28 (t (:default
"libphotogrammetrie")))
30 (defparameter *standard-coordinates
* 4326
31 "EPSG code of the coordinate system that we use for communication.")
33 (defparameter *postgresql-credentials
* nil
34 "A list: (database user password host &key (port 5432) use-ssl)")
36 (defparameter *photogrammetry-mutex
* (bt:make-lock
"photogrammetry"))
38 (setf *read-default-float-format
* 'double-float
)
40 (defparameter *phoros-server
* nil
"Hunchentoot acceptor.")
41 (defparameter *common-root
* nil
"Root directory; contains directories of measuring data.")
42 (defparameter *verbose
* 0 "Integer (interpreted as a bit mask) denoting various kinds of debugging output.")
44 (defun check-db (db-credentials)
45 "Check postgresql connection. Return t if successful; show error on
46 *error-output* otherwise. db-credentials is a list like so: (database
47 user password host &key (port 5432) use-ssl)."
50 (setf connection
(apply #'connect db-credentials
))
51 (error (e) (format *error-output
* "Database connection ~S failed: ~A~&"
54 (disconnect connection
)
57 (defmethod hunchentoot:session-cookie-name
(acceptor)
58 (declare (ignore acceptor
))
61 (defun start-server (&key
(server-port 8080) (common-root "/"))
62 (setf *phoros-server
* (make-instance 'hunchentoot
:acceptor
:port server-port
))
63 (setf *session-max-time
* (* 3600 24))
64 (setf *common-root
* common-root
)
65 (setf *show-lisp-errors-p
* (logbitp 16 *verbose
*))
66 (setf *ps-print-pretty
* (logbitp 15 *verbose
*))
67 ;; Doesn't seem to exist(setf *show-lisp-backtraces-p* t) ;TODO: tie this to --debug option
68 (setf *message-log-pathname
* "hunchentoot-messages.log") ;TODO: try using cl-log
69 (setf *access-log-pathname
* "hunchentoot-access.log") ;TODO: try using cl-log
70 (check-db *postgresql-credentials
*)
71 (with-connection *postgresql-credentials
*
72 (assert-phoros-db-major-version))
73 (hunchentoot:start
*phoros-server
*))
75 (defun stop-server () (hunchentoot:stop
*phoros-server
*))
77 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
78 (register-sql-operators :2+-ary
:&& :overlaps
))
80 (define-easy-handler phoros-handler
()
81 "First HTTP contact: if necessary, check credentials, establish new session."
82 (with-connection *postgresql-credentials
*
83 (let* ((presentation-project-name
84 (second (cl-utilities:split-sequence
#\
/ (script-name*) :remove-empty-subseqs t
)))
85 (presentation-project-id
88 (:select
'presentation-project-id
89 :from
'sys-presentation-project
90 :where
(:= 'presentation-project-name presentation-project-name
))
93 ((null presentation-project-id
) "No such project.")
94 ((and (equal (session-value 'presentation-project-name
) presentation-project-name
)
95 (session-value 'authenticated-p
))
96 (redirect "/view" :add-session-id t
))
99 (setf (session-value 'presentation-project-name
) presentation-project-name
100 (session-value 'presentation-project-id
) presentation-project-id
)
101 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
102 (:form
:method
"post" :enctype
"multipart/form-data"
103 :action
"/authenticate"
105 (:input
:type
"text" :name
"user-name") :br
107 (:input
:type
"password" :name
"user-password") :br
108 (:input
:type
"submit" :value
"Submit")))))))))
110 (pushnew (create-prefix-dispatcher "/phoros/" 'phoros-handler
)
113 (define-easy-handler (authenticate-handler :uri
"/authenticate" :default-request-type
:post
) ()
114 (with-connection *postgresql-credentials
*
115 (let* ((user-name (post-parameter "user-name"))
116 (user-password (post-parameter "user-password"))
117 (presentation-project-id (session-value 'presentation-project-id
))
119 (when presentation-project-id
121 (:select
'user-full-name
122 :from
'sys-user-role
'sys-user
124 (:= 'presentation-project-id presentation-project-id
)
125 (:= 'sys-user-role.user-id
'sys-user.user-id
)
126 (:= 'user-name user-name
)
127 (:= 'user-password user-password
)))
131 (setf (session-value 'authenticated-p
) t
132 (session-value 'user-name
) user-name
133 (session-value 'user-full-name
) user-full-name
)
134 (redirect "/view" :add-session-id t
))
137 (define-easy-handler (logout :uri
"/logout") ()
138 (if (session-verify *request
*)
139 (progn (remove-session *session
*)
143 (define-easy-handler (test :uri
"/test") ()
146 (define-easy-handler (local-data :uri
"/local-data" :default-request-type
:post
) ()
147 "Receive coordinates, respond with the count nearest json objects containing picture url, calibration parameters, and car position, wrapped in an array."
148 (when (session-value 'authenticated-p
)
149 (let* ((presentation-project-id (session-value 'presentation-project-id
))
150 (common-table-names (common-table-names presentation-project-id
))
151 (data (json:decode-json-from-string
(raw-post-data)))
152 (longitude-input (cdr (assoc :longitude data
)))
153 (latitude-input (cdr (assoc :latitude data
)))
154 (count (cdr (assoc :count data
)))
155 (zoom-input (cdr (assoc :zoom data
)))
156 ;;(snap-distance (* 10d-5 (expt 2 (- 18 zoom-input)))) ; assuming geographic coordinates
157 (snap-distance (* 10d-1
(expt 2 (- 18 zoom-input
)))) ; assuming geographic coordinates
159 (format nil
"POINT(~F ~F)" longitude-input latitude-input
))
162 (with-connection *postgresql-credentials
*
164 for common-table-name in common-table-names
170 'date
;TODO: debug only
171 'measurement-id
'recorded-device-id
'device-stage-of-life-id
;TODO: debug only
173 'filename
'byte-position
'point-id
175 ;'coordinates ;the search target
176 'longitude
'latitude
'ellipsoid-height
178 'east-sd
'north-sd
'height-sd
179 'roll
'pitch
'heading
'roll-sd
'pitch-sd
'heading-sd
180 'sensor-width-pix
'sensor-height-pix
'pix-size
182 'dx
'dy
'dz
'omega
'phi
'kappa
183 'c
'xh
'yh
'a1
'a2
'a3
'b1
'b2
'c1
'c2
'r0
184 'b-dx
'b-dy
'b-dz
'b-rotx
'b-roty
'b-rotz
185 'b-ddx
'b-ddy
'b-ddz
'b-drotx
'b-droty
'b-drotz
187 (aggregate-view-name common-table-name
)
189 (:and
(:= 'presentation-project-id presentation-project-id
)
190 (:st_dwithin
'coordinates
191 (:st_geomfromtext point-form
*standard-coordinates
*)
193 (:st_distance
'coordinates
194 (:st_geomfromtext point-form
*standard-coordinates
*)))
197 (json:encode-json-to-string result
))))
199 (defun common-table-names (presentation-project-id)
200 "Return a list of common-table-names of table sets that contain data
201 of presentation project with presentation-project-id."
203 (with-connection *postgresql-credentials
*
205 (:select
'common-table-name
207 :from
'sys-presentation
'sys-measurement
'sys-acquisition-project
209 (:= 'sys-presentation.presentation-project-id presentation-project-id
)
210 (:= 'sys-presentation.measurement-id
'sys-measurement.measurement-id
)
211 (:= 'sys-measurement.acquisition-project-id
'sys-acquisition-project.acquisition-project-id
)))
213 (condition (c) (cl-log:log-message
:server
"While fetching common-table-names of presentation-project-id ~D: ~A" presentation-project-id c
))))
215 (define-easy-handler (points :uri
"/points") (bbox)
216 "Send a bunch of GeoJSON-encoded points from inside bbox to client."
217 (when (session-value 'authenticated-p
)
220 (concatenate 'string
"BOX3D("
221 (substitute #\Space
#\
,
222 (substitute #\Space
#\
, bbox
:count
1)
223 :from-end t
:count
1)
225 (common-table-names (common-table-names (session-value 'presentation-project-id
))))
226 (with-connection *postgresql-credentials
*
227 (json:encode-json-alist-to-string
229 'type
'*geometry-collection
234 (acons 'coordinates x nil
)))
236 for common-table-name in common-table-names
237 for point-table-name
= (make-symbol (concatenate 'string
"dat-" common-table-name
"-point"))
239 (query (:select
(:st_x
(:st_transform
'coordinates
*standard-coordinates
*))
240 (:st_y
(:st_transform
'coordinates
*standard-coordinates
*))
241 :from point-table-name
242 :where
(:&& (:st_transform
'coordinates
*standard-coordinates
*)
243 (:st_setsrid
(:type box3d-form box3d
) *standard-coordinates
*))))))
245 (condition (c) (cl-log:log-message
:server
"While fetching points from inside bbox ~S: ~A" bbox c
)))))
247 (define-easy-handler photo-handler
248 ((bayer-pattern :init-form
"#00ff00,#ff0000")
249 (color-raiser :init-form
"1,1,1"))
250 "Serve an image from a .pictures file."
251 (when (session-value 'authenticated-p
)
253 (let* ((s (cdr (cl-utilities:split-sequence
#\
/ (script-name*)
254 :remove-empty-subseqs t
)))
255 (directory (butlast s
2))
256 (file-name-and-type (cl-utilities:split-sequence
257 #\.
(first (last s
2))))
258 (byte-position (parse-integer (car (last s
)) :junk-allowed t
))
263 :directory
(append (pathname-directory *common-root
*)
264 directory
'(:wild-inferiors
))
265 :name
(first file-name-and-type
)
266 :type
(second file-name-and-type
)))))
268 (setf (content-type*) "image/png")
269 (setf stream
(send-headers))
270 (send-png stream path-to-file byte-position
271 :bayer-pattern
(canonicalize-bayer-pattern bayer-pattern
)
272 :color-raiser
(canonicalize-color-raiser color-raiser
)))
273 (condition (c) (cl-log:log-message
:server
"While serving image ~S: ~A" (request-uri*) c
)))))
275 (pushnew (create-prefix-dispatcher "/photo" 'photo-handler
)
278 (pushnew (create-folder-dispatcher-and-handler "/lib/" "") ;TODO: is this secure enough?
281 (define-easy-handler (phoros.js
:uri
"/phoros.js") ()
282 "Serve some Javascript."
283 (when (session-value 'authenticated-p
)
287 (@ *open-layers
*control
*click
)
288 ((@ *open-layers
*class
)
289 (@ *open-layers
*control
)
290 (create :default-handler-options
299 (@ this handler-options
) ((@ *open-layers
*util extend
)
301 (@ this default-handler-options
)))
302 ((@ *open-layers
*control prototype initialize apply
)
304 (setf (@ this handler
)
305 (new ((@ *open-layers
*handler
*click
) this
306 (create :click
(@ this trigger
))
307 (@ this handler-options
))))))))
310 (new ((@ *open-layers
*projection
) "EPSG:4326")))
311 (setf spherical-mercator
312 (new ((@ *open-layers
*projection
) "EPSG:900913")))
314 (defvar images
(array) "Collection of the photos currently shown.")
315 (defvar streetmap
"The streetmap shown to the user.")
316 (defvar streetmap-estimated-position-layer
)
319 "Anything necessary to deal with a photo."
320 (setf (getprop this
'map
)
321 (new ((getprop *open-layers
'*map
)
322 (create projection spherical-mercator
324 (getprop this
'dummy
) false
;TODO why? (omitting splices map components directly into *image)
327 (setf (getprop *image
'prototype
'show-photo
) show-photo
)
328 (setf (getprop *image
'prototype
'draw-epipolar-line
) draw-epipolar-line
)
329 (setf (getprop *image
'prototype
'draw-active-point
) draw-active-point
)
330 (setf (getprop *image
'prototype
'draw-estimated-positions
)
331 draw-estimated-positions
)
333 (defun photo-path (photo-parameters)
334 "Create from stuff found in photo-parameters a path for use in
336 (+ "/photo/" (@ photo-parameters directory
) "/"
337 (@ photo-parameters filename
) "/"
338 (@ photo-parameters byte-position
) ".png"))
340 (defun has-layer-p (map layer-name
)
341 "False if map doesn't have a layer called layer-name."
342 (chain map
(get-layers-by-name layer-name
) length
))
344 (defun some-active-point-p ()
345 "False if no image in images has an Active Point."
348 sum
(has-layer-p (getprop i
'map
) "Active Point")))
350 (defun remove-layer (map layer-name
)
351 "Destroy layer layer-name in map."
352 (when (has-layer-p map layer-name
)
353 (chain map
(get-layers-by-name layer-name
) 0 (destroy))))
355 (defun remove-any-layers (layer-name)
356 "Destroy in all images and in streetmap the layer named layer-name."
358 for i across images do
(remove-layer (getprop i
'map
) layer-name
))
359 (remove-layer streetmap layer-name
))
361 (defun remove-work-layers ()
362 "Destroy user-generated layers in streetmap and in all images."
363 (remove-any-layers "Epipolar Line")
364 (remove-any-layers "Active Point")
365 (remove-any-layers "Estimated Position")
366 (setf pristine-images-p t
))
368 (defun present-photos ()
369 "Handle the response triggered by request-photos."
370 (let ((photo-parameters ((@ *json
* parse
)
371 (@ photo-request-response response-text
))))
373 for p across photo-parameters
376 (setf (getprop i
'photo-parameters
) p
)
377 ((getprop i
'show-photo
)))
378 ;; (setf (@ (aref photo-parameters 0) angle180) 1) ; Debug: coordinate flipping
381 (defun request-photos (event)
382 "Handle the response to a click into streetmap; fetch photo data."
383 (remove-any-layers "Estimated Position")
385 ((@ ((@ streetmap get-lon-lat-from-pixel
) (@ event xy
)) transform
)
386 spherical-mercator
; why?
389 ((@ *json
* stringify
)
390 (create :longitude
(@ lonlat lon
) ; TODO: use OpenLayer's JSON.
391 :latitude
(@ lonlat lat
)
392 :zoom
((@ streetmap get-zoom
))
394 (setf photo-request-response
395 ((@ *open-layers
*Request
*POST
*)
396 (create :url
"local-data"
398 :headers
(create "Content-type" "text/plain"
399 "Content-length" (@ content length
))
400 :success present-photos
)))))
402 (defun draw-epipolar-line ()
403 "Draw an epipolar line from response triggered by clicking
404 into a (first) photo."
405 (let ((epipolar-line ((@ *json
* parse
)
406 (@ this epipolar-request-response response-text
))))
407 (chain this epipolar-layer
409 (new ((@ *open-layers
*feature
*vector
)
410 (new ((@ *open-layers
*geometry
*line-string
)
411 ((@ epipolar-line map
)
413 (new ((@ *open-layers
*geometry
*point
)
414 (@ x
:m
) (@ x
:n
)))))))))))))
415 ;; either *line-string or *multi-point are usable
417 (defun draw-estimated-positions ()
418 "Draw into streetmap and into all images points at Estimated
419 Position. Estimated Position is the point returned so far from
420 photogrammetric calculations that are triggered by clicking into
422 (let* ((estimated-positions-request-response
425 'estimated-positions-request-response
428 (aref estimated-positions-request-response
0))
430 (aref estimated-positions-request-response
1)))
431 ((@ console log
) global-position
)
432 (setf streetmap-estimated-position-layer
433 (new ((@ *open-layers
*layer
*vector
) "Estimated Position")))
434 (chain streetmap-estimated-position-layer
436 (new ((@ *open-layers
*feature
*vector
)
437 ((@ (new ((@ *open-layers
*geometry
*point
)
438 (getprop global-position
'longitude
)
439 (getprop global-position
'latitude
)))
440 transform
) geographic spherical-mercator
)))))
441 ((@ streetmap add-layer
) streetmap-estimated-position-layer
)
444 for p in estimated-positions
446 (setf (@ i estimated-position-layer
)
447 (new ((@ *open-layers
*layer
*vector
) "Estimated Position")))
448 ((@ i map add-layer
) (@ i estimated-position-layer
))
449 (chain i estimated-position-layer
451 (new ((@ *open-layers
*feature
*vector
)
452 (new ((@ *open-layers
*geometry
*point
)
454 (getprop p
'n
))))))))))
456 (defun draw-active-point ()
457 "Draw an Active Point, i.e. a point used in subsequent
458 photogrammetric calculations."
459 (chain this active-point-layer
461 (new ((@ *open-layers
*feature
*vector
)
462 (new ((@ *open-layers
*geometry
*point
)
463 (getprop this
'photo-parameters
'm
)
464 (getprop this
'photo-parameters
'n
))))))))
466 (defun image-click-action (clicked-image)
468 "Do appropriate things when an image is clicked into."
470 ((@ (@ clicked-image map
) get-lon-lat-from-view-port-px
)
473 (getprop clicked-image
'photo-parameters
))
474 pristine-image-p content request
)
475 (setf (@ photo-parameters m
) (@ lonlat lon
)
476 (@ photo-parameters n
) (@ lonlat lat
))
477 (remove-layer (getprop clicked-image
'map
) "Active Point")
478 (remove-any-layers "Epipolar Line")
479 (setf pristine-images-p
(not (some-active-point-p)))
480 (setf (@ clicked-image active-point-layer
)
481 (new ((@ *open-layers
*layer
*vector
) "Active Point")))
482 ((@ clicked-image map add-layer
)
483 (@ clicked-image active-point-layer
))
484 ((getprop clicked-image
'draw-active-point
))
489 for i across images do
490 (unless (== i clicked-image
)
492 (@ i epipolar-layer
) (new ((@ *open-layers
*layer
*vector
)
494 content
((@ *json
* stringify
)
495 (append (array photo-parameters
)
496 (@ i photo-parameters
)))
497 (@ i epipolar-request-response
)
498 ((@ *open-layers
*Request
*POST
*)
499 (create :url
"epipolar-line"
501 :headers
(create "Content-type" "text/plain"
504 :success
(getprop i
'draw-epipolar-line
)
506 ((@ i map add-layer
) (@ i epipolar-layer
)))))
508 (remove-any-layers "Epipolar Line")
509 (remove-any-layers "Estimated Position")
510 (let* ((active-pointed-photo-parameters
513 when
(has-layer-p (getprop i
'map
) "Active Point")
514 collect
(getprop i
'photo-parameters
)))
516 ((@ *json
* stringify
)
517 (list active-pointed-photo-parameters
521 x
'photo-parameters
))))))))
522 (setf (@ clicked-image estimated-positions-request-response
)
523 ((@ *open-layers
*Request
*POST
*)
524 (create :url
"estimated-positions"
526 :headers
(create "Content-type" "text/plain"
529 :success
(getprop clicked-image
530 'draw-estimated-positions
)
531 :scope clicked-image
)))))))))
533 "Show the photo described in this object's photo-parameters."
535 repeat
((getprop this
'map
'get-num-layers
))
536 do
((getprop this
'map
'layers
0 'destroy
)))
537 ((getprop this
'map
'add-layer
)
538 (new ((@ *open-layers
*layer
*image
)
540 (photo-path (getprop this
'photo-parameters
))
541 (new ((@ *open-layers
*bounds
) -
.5 -
.5
542 (+ (getprop this
'photo-parameters
'sensor-width-pix
)
544 (+ (getprop this
'photo-parameters
'sensor-height-pix
)
545 .5))) ; coordinates shown
546 (new ((@ *open-layers
*size
) 512 256))
548 ((getprop this
'map
'zoom-to-extent
)
549 (new ((@ *open-layers
*bounds
) -
.5 -
.5
550 (1+ (getprop this
'photo-parameters
'sensor-width-pix
))
551 (1+ (getprop this
'photo-parameters
'sensor-height-pix
)))))) ; in coordinates shown
553 (defun initialize-image (image-index)
554 "Create an image usable for displaying photos at position
555 image-index in array images."
556 (setf (aref images image-index
) (new *image
))
557 (setf (@ (aref images image-index
) image-click-action
)
558 (image-click-action (aref images image-index
)))
559 (setf (@ (aref images image-index
) click
)
560 (new ((@ *open-layers
*control
*click
)
561 (create :trigger
(@ (aref images image-index
)
562 image-click-action
)))))
563 ((@ (aref images image-index
) map add-control
)
564 (@ (aref images image-index
) click
))
565 ((@ (aref images image-index
) click activate
))
566 ((@ (aref images image-index
) map add-control
)
567 (new ((@ *open-layers
*control
*mouse-position
))))
568 ((@ (aref images image-index
) map add-control
)
569 (new ((@ *open-layers
*control
*layer-switcher
))))
570 ((@ (aref images image-index
) map render
) (+ image-index
"")))
573 "Prepare user's playground."
574 (setf streetmap
(new ((@ *open-layers
*map
) "streetmap"
575 (create projection geographic
576 display-projection geographic
))))
578 (new ((@ *open-layers
*layer
*vector
) "Survey"
580 (array (new ((@ *open-layers
*strategy
*bbox
*)
581 (create :ratio
1.1))))
583 (new ((@ *open-layers
*protocol
*http
*)
587 (new ((@ *open-layers
*format
*geo-j-s-o-n
)
589 external-projection geographic
590 internal-projection geographic
))))))))))
591 ;;(google (new ((@ *open-layers *Layer *google) "Google Streets")))
592 (osm-layer (new ((@ *open-layers
*layer
*osm
*))))
593 (click-streetmap (new ((@ *open-layers
*control
*click
)
594 (create :trigger request-photos
)))))
595 ((@ streetmap add-control
) click-streetmap
)
596 ((@ click-streetmap activate
))
597 ;;((@ map add-layers) (array osm-layer google survey-layer))
598 ((@ streetmap add-layers
) (array survey-layer osm-layer
))
599 ((@ streetmap add-control
)
600 (new ((@ *open-layers
*control
*layer-switcher
))))
601 ((@ streetmap add-control
)
602 (new ((@ *open-layers
*control
*mouse-position
))))
603 ((@ streetmap zoom-to-extent
)
604 ((@ (new ((@ *open-layers
*bounds
)
605 14.32066 51.72693 14.32608 51.72862))
606 transform
) geographic spherical-mercator
)))
610 (initialize-image i
))))))
612 (define-easy-handler (view :uri
"/view" :default-request-type
:post
) ()
613 "Serve the client their main workspace."
615 (session-value 'authenticated-p
)
616 (who:with-html-output-to-string
(s nil
:indent t
)
618 :xmlns
"http://www.w3.org/1999/xhtml"
623 "Phoros: " (session-value 'presentation-project-name
))))
624 ;;(:link :rel "stylesheet" :href "lib/theme/default/style.css" :type "text/css")
625 (:link
:rel
"stylesheet" :href
"lib/style.css" :type
"text/css")
626 (:script
:src
"lib/openlayers/lib/OpenLayers.js")
627 (:script
:src
"lib/openlayers/lib/proj4js.js") ;TODO: we should be able to make this redundant.
628 (:script
:src
"/phoros.js")
629 ;;(:script :src "http://maps.google.com/maps/api/js?sensor=false")
631 (:body
:onload
(ps (init))
632 (:h1
:id
"title" (who:str
(concatenate 'string
"Phoros: " (session-value 'presentation-project-name
))))
634 "unfinished prototype")
635 (:div
:id
"finish-point-button" :style
"float:left" (:button
:type
"button" :onclick
(ps ()) "finish point"))
636 (:div
:id
"remove-work-layers-button" :style
"float:left" (:button
:type
"button" :onclick
(ps (remove-work-layers)) "start over (keep photos)"))
637 (:div
:id
"blurb-button" :style
"float:left" (:button
:type
"button" :onclick
"self.location.href = \"/blurb\"" "blurb"))
638 (:div
:id
"logout-button" :style
"float:left" (:button
:type
"button" :onclick
"self.location.href = \"/logout\"" "bye"))
640 (:div
:style
"clear:both"
641 (:div
:id
"streetmap" :class
"smallmap" :style
"float:left")
644 (who:htm
(:div
:id i
:class
"image" :style
"float:left")))))))
646 (concatenate 'string
"/phoros/" (session-value 'presentation-project-name
))
649 (define-easy-handler (epipolar-line :uri
"/epipolar-line") ()
650 "Receive vector of two sets of picture parameters, respond with
651 JSON encoded epipolar-lines."
652 (when (session-value 'authenticated-p
)
653 (let* ((data (json:decode-json-from-string
(raw-post-data))))
654 (json:encode-json-to-string
(photogrammetry :epipolar-line
(first data
) (second data
))))))
656 (define-easy-handler (estimated-positions :uri
"/estimated-positions") ()
657 "Receive a two-part JSON vector comprising (1) a vector containing
658 sets of picture-parameters including clicked points stored in :m, :n;
659 and (2) a vector containing sets of picture-parameters; respond with
660 a JSON encoded two-part vector comprising (1) a point in global
661 coordinates; and (2) a vector of image coordinates (m, n) for the
662 global point that correspond to the images from the received second
663 vector. TODO: report error on bad data (ex: points too far apart)."
664 ;; TODO: global-point-for-display should probably contain a proj string in order to make sense of the (cartesian) standard deviations.
665 (when (session-value 'authenticated-p
)
666 (let* ((data (json:decode-json-from-string
(raw-post-data)))
667 (active-point-photo-parameters (first data
))
668 (destination-photo-parameters (second data
))
669 (cartesian-system (cdr (assoc :cartesian-system
(first active-point-photo-parameters
))))
670 (global-point-cartesian (photogrammetry :multi-position-intersection active-point-photo-parameters
))
671 (global-point-geographic-radians
672 (proj:cs2cs
(list (cdr (assoc :x-global global-point-cartesian
))
673 (cdr (assoc :y-global global-point-cartesian
))
674 (cdr (assoc :z-global global-point-cartesian
)))
675 :source-cs cartesian-system
))
676 (global-point-for-display ;points geographic cs, degrees; std deviations in cartesian cs
677 (pairlis '(:longitude
:latitude
:ellipsoid-height
678 :stdx-global
:stdy-global
:stdz-global
)
680 (proj:radians-to-degrees
(first global-point-geographic-radians
))
681 (proj:radians-to-degrees
(second global-point-geographic-radians
))
682 (third global-point-geographic-radians
)
683 (cdr (assoc :stdx-global global-point-cartesian
))
684 (cdr (assoc :stdy-global global-point-cartesian
))
685 (cdr (assoc :stdz-global global-point-cartesian
)))))
688 for i in destination-photo-parameters
689 collect
(photogrammetry :reprojection i global-point-cartesian
))))
690 (json:encode-json-to-string
691 (list global-point-for-display image-coordinates
)))))
693 (define-easy-handler (multi-position-intersection :uri
"/intersection") ()
694 "Receive vector of sets of picture parameters, respond with stuff."
695 (when (session-value 'authenticated-p
)
696 (let* ((data (json:decode-json-from-string
(raw-post-data))))
697 (json:encode-json-to-string
(photogrammetry :multi-position-intersection data
)))))
699 (defgeneric photogrammetry
(mode photo-1
&optional photo-2
)
700 (:documentation
"Call to photogrammetry library. Dispatch on mode."))
702 (defmethod photogrammetry :around
(mode clicked-photo
&optional other-photo
)
703 "Prepare and clean up a run of photogrammetry."
704 (declare (ignore other-photo
))
705 (bt:with-lock-held
(*photogrammetry-mutex
*)
711 (defmethod photogrammetry ((mode (eql :epipolar-line
)) clicked-photo
&optional other-photo
)
712 "Return in an alist an epipolar line in coordinates of other-photo from m and n in clicked-photo."
713 (add-cam* clicked-photo
)
714 (add-bpoint* clicked-photo
)
715 (add-global-car-reference-point* clicked-photo t
)
716 (add-cam* other-photo
)
717 (add-global-car-reference-point* other-photo t
)
719 for i
= 2d0 then
(* i
1.4) until
(> i
50)
721 (set-distance-for-epipolar-line i
)
722 when
(ignore-errors (calculate))
723 collect
(pairlis '(:m
:n
) (list (flip-m-maybe (get-m) other-photo
)
724 (flip-n-maybe (get-n) other-photo
)))))
726 (defmethod photogrammetry ((mode (eql :reprojection
)) photo
&optional global-point
)
727 "Calculate reprojection from photo."
729 (add-global-measurement-point* global-point
)
730 (add-global-car-reference-point* photo
)
731 (set-global-reference-frame)
734 (list (flip-m-maybe (get-m) photo
) (flip-n-maybe (get-n) photo
))))
736 (defmethod photogrammetry ((mode (eql :multi-position-intersection
)) photos
&optional other-photo
)
737 "Calculate intersection from photos."
738 (declare (ignore other-photo
))
739 (set-global-reference-frame)
745 (add-global-car-reference-point* photo t
))
747 (pairlis '(:x-global
:y-global
:z-global
748 :stdx-global
:stdy-global
:stdz-global
)
750 (get-x-global) (get-y-global) (get-z-global)
751 (get-stdx-global) (get-stdy-global) (get-stdz-global))))
753 (defmethod photogrammetry ((mode (eql :intersection
)) photo
&optional other-photo
)
754 "Calculate intersection from two photos that are taken out of the
755 same local coordinate system. (Used for debugging only)."
758 (add-cam* other-photo
)
759 (add-bpoint* other-photo
)
761 (pairlis '(:x-local
:y-local
:z-local
762 :stdx-local
:stdy-local
:stdz-local
)
764 (get-x-local) (get-y-local) (get-z-local)
765 (get-stdx-local) (get-stdy-local) (get-stdz-local)
766 (get-x-global) (get-y-global) (get-z-global))))
768 (defmethod photogrammetry ((mode (eql :mono
)) photo
&optional floor
)
769 "Return in an alist the intersection point of the ray through m and n in photo, and floor."
772 (add-ref-ground-surface* floor
)
773 (add-global-car-reference-point* photo
)
774 (set-global-reference-frame)
776 (pairlis '(:x-global
:y-global
:z-global
)
778 (get-x-global) (get-y-global) (get-z-global))))
780 (defun flip-m-maybe (m photo
)
781 "Flip coordinate m when :mounting-angle in photo suggests it necessary."
782 (if (= 180 (cdr (assoc :mounting-angle photo
)))
783 (- (cdr (assoc :sensor-width-pix photo
)) m
)
785 (defun flip-n-maybe (n photo
)
786 "Flip coordinate n when :mounting-angle in photo suggests it necessary."
787 (if (zerop (cdr (assoc :mounting-angle photo
)))
788 (- (cdr (assoc :sensor-height-pix photo
)) n
)
791 (defun photogrammetry-arglist (alist &rest keys
)
792 "Construct an arglist from alist values corresponding to keys."
793 (mapcar #'(lambda (x) (cdr (assoc x alist
))) keys
))
795 (defun add-cam* (photo-alist)
796 "Call add-cam with arguments taken from photo-alist."
798 (photogrammetry-arglist
799 photo-alist
:sensor-height-pix
:sensor-width-pix
))
801 (mapcar #'(lambda (x) (coerce x
'double-float
))
802 (photogrammetry-arglist photo-alist
804 :dx
:dy
:dz
:omega
:phi
:kappa
806 :a-1
:a-2
:a-3
:b-1
:b-2
:c-1
:c-2
:r-0
807 :b-dx
:b-dy
:b-dz
:b-ddx
:b-ddy
:b-ddz
808 :b-rotx
:b-roty
:b-rotz
809 :b-drotx
:b-droty
:b-drotz
))))
810 (apply #'add-cam
(nconc integer-args double-float-args
))))
812 (defun add-bpoint* (photo-alist)
813 "Call add-bpoint with arguments taken from photo-alist."
814 (add-bpoint (coerce (flip-m-maybe (cdr (assoc :m photo-alist
)) photo-alist
) 'double-float
)
815 (coerce (flip-n-maybe (cdr (assoc :n photo-alist
)) photo-alist
) 'double-float
)))
817 (defun add-ref-ground-surface* (floor-alist)
818 "Call add-ref-ground-surface with arguments taken from floor-alist."
819 (let ((double-float-args
820 (mapcar #'(lambda (x) (coerce x
'double-float
))
821 (photogrammetry-arglist floor-alist
823 (apply #'add-ref-ground-surface double-float-args
)))
825 (defun add-global-car-reference-point* (photo-alist &optional cam-set-global-p
)
826 "Call add-global-car-reference-point with arguments taken from photo-alist. When cam-set-global-p is t, call add-global-car-reference-point-cam-set-global instead."
827 (let* ((longitude-radians (proj:degrees-to-radians
(car (photogrammetry-arglist photo-alist
:longitude
))))
828 (latitude-radians (proj:degrees-to-radians
(car (photogrammetry-arglist photo-alist
:latitude
))))
829 (ellipsoid-height (car (photogrammetry-arglist photo-alist
:ellipsoid-height
)))
830 (destination-cs (car (photogrammetry-arglist photo-alist
:cartesian-system
)))
831 (cartesian-coordinates
832 (proj:cs2cs
(list longitude-radians latitude-radians ellipsoid-height
)
833 :destination-cs destination-cs
))
835 (mapcar #'(lambda (x) (coerce x
'double-float
))
836 (photogrammetry-arglist photo-alist
837 :roll
:pitch
:heading
838 :latitude
:longitude
)))
840 (nconc cartesian-coordinates other-args
)))
841 (apply (if cam-set-global-p
842 #'add-global-car-reference-point-cam-set-global
843 #'add-global-car-reference-point
)
846 (defun add-global-measurement-point* (point)
847 "Call add-global-measurement-point with arguments taken from point."
848 (let ((double-float-args
849 (mapcar #'(lambda (x) (coerce x
'double-float
))
850 (photogrammetry-arglist point
851 :x-global
:y-global
:z-global
))))
852 (apply #'add-global-measurement-point double-float-args
)))