Photogrammetry library loading fixed.
[phoros.git] / phoros.lisp
blob10a78fa5f2fce7f41f937c80c27052ceadb7ff1f
1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2010, 2011 Bert Burgemeister
3 ;;;
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.
8 ;;;
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.
13 ;;;
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.
18 (in-package :phoros)
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)."
48 (let (connection)
49 (handler-case
50 (setf connection (apply #'connect db-credentials))
51 (error (e) (format *error-output* "Database connection ~S failed: ~A~&"
52 db-credentials e)))
53 (when connection
54 (disconnect connection)
55 t)))
57 (defmethod hunchentoot:session-cookie-name (acceptor)
58 (declare (ignore acceptor))
59 "phoros-session")
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
86 (ignore-errors
87 (query
88 (:select 'presentation-project-id
89 :from 'sys-presentation-project
90 :where (:= 'presentation-project-name presentation-project-name))
91 :single))))
92 (cond
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))
98 (progn
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"
104 "User:" :br
105 (:input :type "text" :name "user-name") :br
106 "Password:" :br
107 (:input :type "password" :name "user-password") :br
108 (:input :type "submit" :value "Submit")))))))))
110 (pushnew (create-prefix-dispatcher "/phoros/" 'phoros-handler)
111 *dispatch-table*)
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))
118 (user-full-name
119 (when presentation-project-id
120 (query
121 (:select 'user-full-name
122 :from 'sys-user-role 'sys-user
123 :where (:and
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)))
128 :single))))
129 (if user-full-name
130 (progn
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))
135 "Rejected."))))
137 (define-easy-handler (logout :uri "/logout") ()
138 (if (session-verify *request*)
139 (progn (remove-session *session*)
140 "Bye.")
141 "Bye (again)."))
143 (define-easy-handler (test :uri "/test") ()
144 "Authenticated.")
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
158 (point-form
159 (format nil "POINT(~F ~F)" longitude-input latitude-input))
160 (result
161 (ignore-errors
162 (with-connection *postgresql-credentials*
163 (loop
164 for common-table-name in common-table-names
165 nconc
166 (query
167 (:limit
168 (:order-by
169 (:select
170 'date ;TODO: debug only
171 'measurement-id 'recorded-device-id 'device-stage-of-life-id ;TODO: debug only
172 'directory
173 'filename 'byte-position 'point-id
174 'trigger-time
175 ;'coordinates ;the search target
176 'longitude 'latitude 'ellipsoid-height
177 'cartesian-system
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
181 'mounting-angle
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
186 :from
187 (aggregate-view-name common-table-name)
188 :where
189 (:and (:= 'presentation-project-id presentation-project-id)
190 (:st_dwithin 'coordinates
191 (:st_geomfromtext point-form *standard-coordinates*)
192 snap-distance)))
193 (:st_distance 'coordinates
194 (:st_geomfromtext point-form *standard-coordinates*)))
195 count)
196 :alists))))))
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."
202 (handler-case
203 (with-connection *postgresql-credentials*
204 (query
205 (:select 'common-table-name
206 :distinct
207 :from 'sys-presentation 'sys-measurement 'sys-acquisition-project
208 :where (:and
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)))
212 :column))
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)
218 (handler-case
219 (let ((box3d-form
220 (concatenate 'string "BOX3D("
221 (substitute #\Space #\,
222 (substitute #\Space #\, bbox :count 1)
223 :from-end t :count 1)
224 ")"))
225 (common-table-names (common-table-names (session-value 'presentation-project-id))))
226 (with-connection *postgresql-credentials*
227 (json:encode-json-alist-to-string
228 (acons
229 'type '*geometry-collection
230 (acons 'geometries
231 (mapcar
232 #'(lambda (x)
233 (acons 'type '*point
234 (acons 'coordinates x nil)))
235 (loop
236 for common-table-name in common-table-names
237 for point-table-name = (make-symbol (concatenate 'string "dat-" common-table-name "-point"))
238 append
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*))))))
244 nil)))))
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)
252 (handler-case
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))
259 (path-to-file
260 (car
261 (directory
262 (make-pathname
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)))))
267 stream)
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)
276 *dispatch-table*)
278 (pushnew (create-folder-dispatcher-and-handler "/lib/" "") ;TODO: is this secure enough?
279 *dispatch-table*)
281 (define-easy-handler (phoros.js :uri "/phoros.js") ()
282 "Serve some Javascript."
283 (when (session-value 'authenticated-p)
286 (setf
287 (@ *open-layers *control *click)
288 ((@ *open-layers *class)
289 (@ *open-layers *control)
290 (create :default-handler-options
291 (create :single t
292 :double false
293 :pixel-tolerance 0
294 :stop-single false
295 :stop-double false)
296 :initialize
297 (lambda (options)
298 (setf
299 (@ this handler-options) ((@ *open-layers *util extend)
300 (create)
301 (@ this default-handler-options)))
302 ((@ *open-layers *control prototype initialize apply)
303 this arguments)
304 (setf (@ this handler)
305 (new ((@ *open-layers *handler *click) this
306 (create :click (@ this trigger))
307 (@ this handler-options))))))))
309 (setf geographic
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)
318 (defun *image ()
319 "Anything necessary to deal with a photo."
320 (setf (getprop this 'map)
321 (new ((getprop *open-layers '*map)
322 (create projection spherical-mercator
323 all-overlays t)))
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
335 a image url."
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."
346 (loop
347 for i across images
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."
357 (loop
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))))
372 (loop
373 for p across photo-parameters
374 for i across images
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")
384 (let ((lonlat
385 ((@ ((@ streetmap get-lon-lat-from-pixel) (@ event xy)) transform)
386 spherical-mercator ; why?
387 geographic)))
388 (setf content
389 ((@ *json* stringify)
390 (create :longitude (@ lonlat lon) ; TODO: use OpenLayer's JSON.
391 :latitude (@ lonlat lat)
392 :zoom ((@ streetmap get-zoom))
393 :count 6)))
394 (setf photo-request-response
395 ((@ *open-layers *Request *POST*)
396 (create :url "local-data"
397 :data content
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
408 (add-features
409 (new ((@ *open-layers *feature *vector)
410 (new ((@ *open-layers *geometry *line-string)
411 ((@ epipolar-line map)
412 (lambda (x)
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
421 another photo."
422 (let* ((estimated-positions-request-response
423 ((@ *json* parse)
424 (getprop this
425 'estimated-positions-request-response
426 'response-text)))
427 (global-position
428 (aref estimated-positions-request-response 0))
429 (estimated-positions
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
435 (add-features
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)
442 (loop
443 for i in images
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
450 (add-features
451 (new ((@ *open-layers *feature *vector)
452 (new ((@ *open-layers *geometry *point)
453 (getprop p 'm)
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
460 (add-features
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)
467 (lambda (event)
468 "Do appropriate things when an image is clicked into."
469 (let* ((lonlat
470 ((@ (@ clicked-image map) get-lon-lat-from-view-port-px)
471 (@ event xy)))
472 (photo-parameters
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))
486 pristine-images-p
487 (progn
488 (loop
489 for i across images do
490 (unless (== i clicked-image)
491 (setf
492 (@ i epipolar-layer) (new ((@ *open-layers *layer *vector)
493 "Epipolar Line"))
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"
500 :data content
501 :headers (create "Content-type" "text/plain"
502 "Content-length"
503 (@ content length))
504 :success (getprop i 'draw-epipolar-line)
505 :scope i)))
506 ((@ i map add-layer) (@ i epipolar-layer)))))
507 (progn
508 (remove-any-layers "Epipolar Line")
509 (remove-any-layers "Estimated Position")
510 (let* ((active-pointed-photo-parameters
511 (loop
512 for i across images
513 when (has-layer-p (getprop i 'map) "Active Point")
514 collect (getprop i 'photo-parameters)))
515 (content
516 ((@ *json* stringify)
517 (list active-pointed-photo-parameters
518 (chain images
519 (map #'(lambda (x)
520 (getprop
521 x 'photo-parameters))))))))
522 (setf (@ clicked-image estimated-positions-request-response)
523 ((@ *open-layers *Request *POST*)
524 (create :url "estimated-positions"
525 :data content
526 :headers (create "Content-type" "text/plain"
527 "Content-length"
528 (@ content length))
529 :success (getprop clicked-image
530 'draw-estimated-positions)
531 :scope clicked-image)))))))))
532 (defun show-photo ()
533 "Show the photo described in this object's photo-parameters."
534 (loop
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)
539 "Photo"
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))
547 (create))))
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 "")))
572 (defun init ()
573 "Prepare user's playground."
574 (setf streetmap (new ((@ *open-layers *map) "streetmap"
575 (create projection geographic
576 display-projection geographic))))
577 (let* ((survey-layer
578 (new ((@ *open-layers *layer *vector) "Survey"
579 (create :strategies
580 (array (new ((@ *open-layers *strategy *bbox*)
581 (create :ratio 1.1))))
582 :protocol
583 (new ((@ *open-layers *protocol *http*)
584 (create
585 :url "points"
586 :format
587 (new ((@ *open-layers *format *geo-j-s-o-n)
588 (create
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)))
607 (loop
608 for i from 0 to 3
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)
617 (:html
618 :xmlns "http://www.w3.org/1999/xhtml"
619 (:head
620 (:title (who:str
621 (concatenate
622 'string
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))))
633 (:p :id "shortdesc"
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")
642 (loop
643 for i from 0 to 3 do
644 (who:htm (:div :id i :class "image" :style "float:left")))))))
645 (redirect
646 (concatenate 'string "/phoros/" (session-value 'presentation-project-name))
647 :add-session-id t)))
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)
679 (list
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)))))
686 (image-coordinates
687 (loop
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*)
706 (del-all)
707 (unwind-protect
708 (call-next-method)
709 (del-all))))
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)
718 (loop
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."
728 (add-cam* photo)
729 (add-global-measurement-point* global-point)
730 (add-global-car-reference-point* photo)
731 (set-global-reference-frame)
732 (calculate)
733 (pairlis '(:m :n)
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)
740 (loop
741 for photo in photos
743 (add-cam* photo)
744 (add-bpoint* photo)
745 (add-global-car-reference-point* photo t))
746 (calculate)
747 (pairlis '(:x-global :y-global :z-global
748 :stdx-global :stdy-global :stdz-global)
749 (list
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)."
756 (add-cam* photo)
757 (add-bpoint* photo)
758 (add-cam* other-photo)
759 (add-bpoint* other-photo)
760 (calculate)
761 (pairlis '(:x-local :y-local :z-local
762 :stdx-local :stdy-local :stdz-local)
763 (list
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."
770 (add-cam* photo)
771 (add-bpoint* photo)
772 (add-ref-ground-surface* floor)
773 (add-global-car-reference-point* photo)
774 (set-global-reference-frame)
775 (calculate)
776 (pairlis '(:x-global :y-global :z-global)
777 (list
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."
797 (let ((integer-args
798 (photogrammetry-arglist
799 photo-alist :sensor-height-pix :sensor-width-pix))
800 (double-float-args
801 (mapcar #'(lambda (x) (coerce x 'double-float))
802 (photogrammetry-arglist photo-alist
803 :pix-size
804 :dx :dy :dz :omega :phi :kappa
805 :c :xh :yh
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
822 :nx :ny :nz :d))))
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))
834 (other-args
835 (mapcar #'(lambda (x) (coerce x 'double-float))
836 (photogrammetry-arglist photo-alist
837 :roll :pitch :heading
838 :latitude :longitude)))
839 (double-float-args
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)
844 double-float-args)))
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)))