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 ;;; Debug helpers. TODO: remove them.
23 (defparameter *t
* nil
)
24 (defparameter *tt
* nil
)
26 (cffi:define-foreign-library phoml
27 (:unix
(:or
"./libphoml.so"
28 "./phoml/lib/libphoml.so"))
29 (t (:default
"libphoml")))
31 (defparameter *standard-coordinates
* 4326
32 "EPSG code of the coordinate system that we use for communication.")
34 (defparameter *postgresql-credentials
* nil
35 "A list: (database user password host &key (port 5432) use-ssl)")
37 (defparameter *photogrammetry-mutex
* (bt:make-lock
"photogrammetry"))
39 (setf *read-default-float-format
* 'double-float
)
41 (defparameter *phoros-server
* nil
"Hunchentoot acceptor.")
43 (defparameter *common-root
* nil
44 "Root directory; contains directories of measuring data.")
46 (defparameter *verbose
* 0
47 "Integer (interpreted as a bit mask) denoting various kinds of
50 (defparameter *use-multi-file-openlayers
* nil
51 "If t, use OpenLayers uncompiled from openlayers/*, which makes
52 debugging easier. Otherwise use a single-file shrunk
55 (defparameter *number-of-images
* 4
56 "Number of photos shown to the HTTP client.")
58 (defparameter *number-of-features-per-layer
* 500
59 "What we think a browser can swallow.")
61 (defun check-db (db-credentials)
62 "Check postgresql connection. Return t if successful; show error on
63 *error-output* otherwise. db-credentials is a list like so: (database
64 user password host &key (port 5432) use-ssl)."
67 (setf connection
(apply #'connect db-credentials
))
68 (error (e) (format *error-output
* "Database connection ~S failed: ~A~&"
71 (disconnect connection
)
74 (defmethod hunchentoot:session-cookie-name
(acceptor)
75 (declare (ignore acceptor
))
78 (defun start-server (&key
(server-port 8080) (common-root "/"))
79 (setf *phoros-server
* (make-instance 'hunchentoot
:acceptor
:port server-port
))
80 (setf *session-max-time
* (* 3600 24))
81 (setf *common-root
* common-root
)
82 (setf *show-lisp-errors-p
* (logbitp 16 *verbose
*))
83 (setf *ps-print-pretty
* (logbitp 15 *verbose
*))
84 (setf *use-multi-file-openlayers
* (logbitp 14 *verbose
*))
85 ;; Doesn't seem to exist(setf *show-lisp-backtraces-p* t) ;TODO: tie this to --debug option
86 (setf *message-log-pathname
* "hunchentoot-messages.log") ;TODO: try using cl-log
87 (setf *access-log-pathname
* "hunchentoot-access.log") ;TODO: try using cl-log
88 (check-db *postgresql-credentials
*)
89 (with-connection *postgresql-credentials
*
90 (assert-phoros-db-major-version))
91 (hunchentoot:start
*phoros-server
*))
93 (defun stop-server () (hunchentoot:stop
*phoros-server
*))
95 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
96 (register-sql-operators :2+-ary
:&& :overlaps
))
98 (define-easy-handler phoros-handler
()
99 "First HTTP contact: if necessary, check credentials, establish new
101 (with-connection *postgresql-credentials
*
102 (let* ((presentation-project-name
103 (second (cl-utilities:split-sequence
#\
/ (script-name*) :remove-empty-subseqs t
)))
104 (presentation-project-id
107 (:select
'presentation-project-id
108 :from
'sys-presentation-project
109 :where
(:= 'presentation-project-name presentation-project-name
))
112 ((null presentation-project-id
) "No such project.") ;TODO: send appropriate http error code
113 ((and (equal (session-value 'presentation-project-name
) presentation-project-name
)
114 (session-value 'authenticated-p
))
115 (redirect "/phoros-lib/view" :add-session-id t
))
118 (setf (session-value 'presentation-project-name
)
119 presentation-project-name
)
120 (setf (session-value 'presentation-project-id
)
121 presentation-project-id
)
122 (setf (session-value 'presentation-project-bbox
)
123 (presentation-project-bbox presentation-project-id
))
124 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
125 (:form
:method
"post" :enctype
"multipart/form-data"
126 :action
"/phoros-lib/authenticate"
128 (:input
:type
"text" :name
"user-name") :br
130 (:input
:type
"password" :name
"user-password") :br
131 (:input
:type
"submit" :value
"Submit")))))))))
133 (pushnew (create-prefix-dispatcher "/phoros/" 'phoros-handler
)
137 (authenticate-handler :uri
"/phoros-lib/authenticate"
138 :default-request-type
:post
)
140 "Check user credentials."
141 (with-connection *postgresql-credentials
*
142 (let* ((user-name (post-parameter "user-name"))
143 (user-password (post-parameter "user-password"))
144 (presentation-project-id (session-value 'presentation-project-id
))
146 (when presentation-project-id
149 'sys-user.user-full-name
151 'sys-user-role.user-role
152 :from
'sys-user-role
'sys-user
154 (:= 'presentation-project-id presentation-project-id
)
155 (:= 'sys-user-role.user-id
'sys-user.user-id
)
156 (:= 'user-name user-name
)
157 (:= 'user-password user-password
)))
159 (user-full-name (first user-info
))
160 (user-id (second user-info
))
161 (user-role (third user-info
)))
164 (setf (session-value 'authenticated-p
) t
165 (session-value 'user-name
) user-name
166 (session-value 'user-full-name
) user-full-name
167 (session-value 'user-id
) user-id
168 (session-value 'user-role
) user-role
)
169 (redirect "/phoros-lib/view" :add-session-id t
))
172 (define-easy-handler logout-handler
()
173 (if (session-verify *request
*)
174 (progn (remove-session *session
*)
178 (pushnew (create-regex-dispatcher "/logout" 'logout-handler
)
182 (local-data :uri
"/phoros-lib/local-data" :default-request-type
:post
)
184 "Receive coordinates, respond with the count nearest json objects
185 containing picture url, calibration parameters, and car position,
186 wrapped in an array."
187 (when (session-value 'authenticated-p
)
188 (let* ((presentation-project-id (session-value 'presentation-project-id
))
189 (common-table-names (common-table-names presentation-project-id
))
190 (data (json:decode-json-from-string
(raw-post-data)))
191 (longitude-input (cdr (assoc :longitude data
)))
192 (latitude-input (cdr (assoc :latitude data
)))
193 (count (cdr (assoc :count data
)))
194 (zoom-input (cdr (assoc :zoom data
)))
195 ;;(snap-distance (* 10d-5 (expt 2 (- 18 zoom-input)))) ; assuming geographic coordinates
196 (snap-distance (* 10d-1
(expt 2 (- 18 zoom-input
)))) ; assuming geographic coordinates
198 (format nil
"POINT(~F ~F)" longitude-input latitude-input
))
201 (with-connection *postgresql-credentials
*
203 for common-table-name in common-table-names
209 'date
;TODO: debug only
210 'measurement-id
'recorded-device-id
'device-stage-of-life-id
;TODO: debug only
212 'filename
'byte-position
'point-id
214 ;'coordinates ;the search target
215 'longitude
'latitude
'ellipsoid-height
217 'east-sd
'north-sd
'height-sd
218 'roll
'pitch
'heading
'roll-sd
'pitch-sd
'heading-sd
219 'sensor-width-pix
'sensor-height-pix
'pix-size
221 'dx
'dy
'dz
'omega
'phi
'kappa
222 'c
'xh
'yh
'a1
'a2
'a3
'b1
'b2
'c1
'c2
'r0
223 'b-dx
'b-dy
'b-dz
'b-rotx
'b-roty
'b-rotz
224 'b-ddx
'b-ddy
'b-ddz
'b-drotx
'b-droty
'b-drotz
226 (aggregate-view-name common-table-name
)
228 (:and
(:= 'presentation-project-id presentation-project-id
)
229 (:st_dwithin
'coordinates
230 (:st_geomfromtext point-form
*standard-coordinates
*)
232 (:st_distance
'coordinates
233 (:st_geomfromtext point-form
*standard-coordinates
*)))
236 (json:encode-json-to-string result
))))
239 (store-point :uri
"/phoros-lib/store-point" :default-request-type
:post
)
241 "Receive point sent by user; store it into database."
242 (when (session-value 'authenticated-p
)
243 (let* ((presentation-project-name (session-value 'presentation-project-name
))
244 (user-id (session-value 'user-id
))
245 (user-role (session-value 'user-role
))
246 (data (json:decode-json-from-string
(raw-post-data)))
247 (longitude-input (cdr (assoc :longitude data
)))
248 (latitude-input (cdr (assoc :latitude data
)))
249 (ellipsoid-height-input (cdr (assoc :ellipsoid-height data
)))
250 (stdx-global (cdr (assoc :stdx-global data
)))
251 (stdy-global (cdr (assoc :stdy-global data
)))
252 (stdz-global (cdr (assoc :stdz-global data
)))
253 (attribute (cdr (assoc :attribute data
)))
254 (description (cdr (assoc :description data
)))
255 (numeric-description (cdr (assoc :numeric-description data
)))
257 (format nil
"SRID=4326; POINT(~S ~S ~S)"
258 longitude-input latitude-input ellipsoid-height-input
))
259 (user-point-table-name
260 (user-point-table-name presentation-project-name
)))
262 (not (string-equal user-role
"read")) ;that is, "write" or "admin"
263 () "No write permission.")
264 (with-connection *postgresql-credentials
*
266 (= 1 (execute (:insert-into user-point-table-name
:set
269 'description description
270 'numeric-description numeric-description
271 'creation-date
'current-timestamp
272 'coordinates
(:st_geomfromewkt point-form
)
273 'stdx-global stdx-global
274 'stdy-global stdy-global
275 'stdz-global stdz-global
277 () "No point stored. This should not happen.")))))
280 (update-point :uri
"/phoros-lib/update-point" :default-request-type
:post
)
282 "Update point sent by user in database."
283 (when (session-value 'authenticated-p
)
284 (let* ((presentation-project-name (session-value 'presentation-project-name
))
285 (user-id (session-value 'user-id
))
286 (user-role (session-value 'user-role
))
287 (data (json:decode-json-from-string
(raw-post-data)))
288 (user-point-id (cdr (assoc :user-point-id data
)))
289 (attribute (cdr (assoc :attribute data
)))
290 (description (cdr (assoc :description data
)))
291 (numeric-description (cdr (assoc :numeric-description data
)))
292 (user-point-table-name
293 (user-point-table-name presentation-project-name
)))
295 (not (string-equal user-role
"read")) ;that is, "write" or "admin"
296 () "No write permission.")
297 (with-connection *postgresql-credentials
*
299 (= 1 (execute (:update user-point-table-name
:set
301 'description description
302 'numeric-description numeric-description
303 'creation-date
'current-timestamp
304 :where
(:and
(:= 'user-point-id user-point-id
)
305 (:= (if (string-equal user-role
"admin")
309 () "No point stored. Did you try to update someone else's point without having admin permission?")))))
312 (delete-point :uri
"/phoros-lib/delete-point" :default-request-type
:post
)
314 "Delete user point if user is allowed to do so."
315 (when (session-value 'authenticated-p
)
316 (let* ((presentation-project-name (session-value 'presentation-project-name
))
317 (user-id (session-value 'user-id
))
318 (user-role (session-value 'user-role
))
319 (user-point-table-name
320 (user-point-table-name presentation-project-name
))
321 (data (json:decode-json-from-string
(raw-post-data))))
322 (with-connection *postgresql-credentials
*
324 (eql 1 (cond ((string-equal user-role
"admin")
325 (execute (:delete-from user-point-table-name
326 :where
(:= 'user-point-id data
))))
327 ((string-equal user-role
"write")
328 (execute (:delete-from user-point-table-name
330 (:= 'user-point-id data
)
331 (:= 'user-id user-id
)))))))
332 () "No point deleted. This should not happen.")))))
335 (defun common-table-names (presentation-project-id)
336 "Return a list of common-table-names of table sets that contain data
337 of presentation project with presentation-project-id."
339 (with-connection *postgresql-credentials
*
341 (:select
'common-table-name
343 :from
'sys-presentation
'sys-measurement
'sys-acquisition-project
345 (:= 'sys-presentation.presentation-project-id presentation-project-id
)
346 (:= 'sys-presentation.measurement-id
'sys-measurement.measurement-id
)
347 (:= 'sys-measurement.acquisition-project-id
'sys-acquisition-project.acquisition-project-id
)))
352 "While fetching common-table-names of presentation-project-id ~D: ~A"
353 presentation-project-id c
))))
355 (defun encode-geojson-to-string (features &rest junk-keys
)
356 "Encode a list of property lists into a GeoJSON FeatureCollection.
357 Each property list must contain keys for coordinates, :x, :y, :z; and
358 for a numeric point :id, followed by zero or more pieces of extra
359 information. The extra information is stored as GeoJSON Feature
360 properties. Exclude property list elements with keys that are in
362 (with-output-to-string (s)
363 (json:with-object
(s)
364 (json:encode-object-member
:type
:*feature-collection s
)
365 (json:as-object-member
(:features s
)
368 #'(lambda (point-with-properties)
369 (dolist (junk-key junk-keys
)
370 (remf point-with-properties junk-key
))
371 (destructuring-bind (&key x y z id
&allow-other-keys
) ;TODO: z probably bogus
372 point-with-properties
373 (json:as-array-member
(s)
374 (json:with-object
(s)
375 (json:encode-object-member
:type
:*feature s
)
376 (json:as-object-member
(:geometry s
)
377 (json:with-object
(s)
378 (json:encode-object-member
:type
:*point s
)
379 (json:as-object-member
(:coordinates s
)
380 (json:encode-json
(list x y z
) s
))))
381 (json:encode-object-member
:id id s
)
382 (json:as-object-member
(:properties s
)
383 (dolist (key '(:x
:y
:z
:id
))
384 (remf point-with-properties key
))
385 (json:encode-json-plist point-with-properties s
))))))
389 "Return a WKT-compliant BOX3D string from string bbox."
390 (concatenate 'string
"BOX3D("
391 (substitute #\Space
#\
,
392 (substitute #\Space
#\
, bbox
:count
1)
393 :from-end t
:count
1)
396 (define-easy-handler (points :uri
"/phoros-lib/points") (bbox)
397 "Send a bunch of GeoJSON-encoded points from inside bbox to client."
398 (when (session-value 'authenticated-p
)
400 (let* ((presentation-project-id (session-value 'presentation-project-id
))
402 (common-table-names presentation-project-id
)))
403 (encode-geojson-to-string
404 (with-connection *postgresql-credentials
*
411 for common-table-name in common-table-names
412 for aggregate-view-name
413 = (aggregate-view-name common-table-name
)
418 (:st_transform
'coordinates
,*standard-coordinates
*))
422 (:st_transform
'coordinates
,*standard-coordinates
*))
426 (:st_transform
'coordinates
,*standard-coordinates
*))
428 (:as
'point-id
'id
) ;becomes fid on client
429 (:as
(:random
) random
)
430 :from
',aggregate-view-name
431 :natural
:left-join
'sys-presentation
434 (:= 'presentation-project-id
,presentation-project-id
)
436 (:st_transform
'coordinates
,*standard-coordinates
*)
437 (:st_setsrid
(:type
,(box3d bbox
) box3d
)
438 ,*standard-coordinates
*))))))
440 ,*number-of-features-per-layer
*))
445 :server
"While fetching points from inside bbox ~S: ~A"
448 (defun presentation-project-bbox (presentation-project-id)
449 "Return bounding box of the entire presentation-project as a string
451 (let* ((common-table-names
452 (common-table-names presentation-project-id
)))
453 (with-connection *postgresql-credentials
*
461 (:st_extent
(:st_transform
'coordinates
,*standard-coordinates
*))
465 for common-table-name in common-table-names
466 for aggregate-view-name
467 = (aggregate-view-name common-table-name
)
471 :from
',aggregate-view-name
472 :natural
:left-join
'sys-presentation
474 (:= 'presentation-project-id
475 ,presentation-project-id
))))
479 (define-easy-handler (user-points :uri
"/phoros-lib/user-points") (bbox)
480 "Send a bunch of GeoJSON-encoded points from inside bbox to client."
481 (when (session-value 'authenticated-p
)
483 (let ((user-point-table-name
484 (user-point-table-name (session-value 'presentation-project-name
))))
485 (encode-geojson-to-string
486 (with-connection *postgresql-credentials
*
492 (:st_x
(:st_transform
'coordinates
*standard-coordinates
*))
495 (:st_y
(:st_transform
'coordinates
*standard-coordinates
*))
498 (:st_z
(:st_transform
'coordinates
*standard-coordinates
*))
500 (:as
'user-point-id
'id
) ;becomes fid on client
505 (:as
(:to-char
'creation-date
"IYYY-MM-DD HH24:MI:SS TZ")
507 :from user-point-table-name
:natural
:left-join
'sys-user
509 (:st_transform
'coordinates
*standard-coordinates
*)
510 (:st_setsrid
(:type
(box3d bbox
) box3d
)
511 *standard-coordinates
*)))
513 *number-of-features-per-layer
*)
517 :server
"While fetching user-points from inside bbox ~S: ~A"
520 (define-easy-handler photo-handler
521 ((bayer-pattern :init-form
"#00ff00,#ff0000")
522 (color-raiser :init-form
"1,1,1"))
523 "Serve an image from a .pictures file."
524 (when (session-value 'authenticated-p
)
526 (let* ((s (cdr (cl-utilities:split-sequence
#\
/ (script-name*)
527 :remove-empty-subseqs t
)))
528 (directory (last (butlast s
2)))
529 (file-name-and-type (cl-utilities:split-sequence
530 #\.
(first (last s
2))))
531 (byte-position (parse-integer (car (last s
)) :junk-allowed t
))
536 :directory
(append (pathname-directory *common-root
*)
537 directory
'(:wild-inferiors
))
538 :name
(first file-name-and-type
)
539 :type
(second file-name-and-type
)))))
541 (setf (content-type*) "image/png")
542 (setf stream
(send-headers))
543 (send-png stream path-to-file byte-position
544 :bayer-pattern
(canonicalize-bayer-pattern bayer-pattern
)
545 :color-raiser
(canonicalize-color-raiser color-raiser
)))
548 :server
"While serving image ~S: ~A" (request-uri*) c
)))))
550 (pushnew (create-prefix-dispatcher "/phoros-lib/photo" 'photo-handler
)
553 ;;; for debugging; this is the multi-file OpenLayers
554 (pushnew (create-folder-dispatcher-and-handler
555 "/phoros-lib/openlayers/" "OpenLayers-2.10/")
558 (pushnew (create-folder-dispatcher-and-handler "/phoros-lib/ol/" "ol/")
561 (pushnew (create-folder-dispatcher-and-handler "/phoros-lib/css/" "css/") ;TODO: merge this style.css into public_html/style.css
564 (pushnew (create-folder-dispatcher-and-handler
565 "/phoros-lib/public_html/" "public_html/")
568 (pushnew (create-static-file-dispatcher-and-handler
569 "/favicon.ico" "public_html/favicon.ico")
572 (define-easy-handler (phoros.js
:uri
"/phoros-lib/phoros.js") ()
573 "Serve some Javascript."
574 (when (session-value 'authenticated-p
)
577 (setf debug-info
(@ *open-layers
*console info
))
579 (defmacro inner-html-with-id
(id)
580 "innerHTML of element with id=\"id\"."
581 `(chain document
(get-element-by-id ,id
) inner-h-t-m-l
))
583 (defvar *help-topics
*
586 (who-ps-html (:p
"User role. \"Read\" can't write anything. \"Write\" may write user points and delete their own ones. \"Admin\" may write user points and delete points written by others."))
587 :presentation-project-name
588 (who-ps-html (:p
"Presentation project name."))
590 (who-ps-html (:p
"Next action."))
592 (who-ps-html (:p
"Store point with its attribute, description and numeric description into database. Afterwards, increment the numeric description if possible."))
594 (who-ps-html (:p
"Delete current point."))
596 (who-ps-html (:p
"One of a few possible point attributes.")
597 (:p
"TODO: currently only the hard-coded ones are available."))
599 (who-ps-html (:p
"Optional verbal description of point."))
600 :point-numeric-description
601 (who-ps-html (:p
"Optional additional description of point. Preferrably numeric and if so, automatically incremented after finishing point."))
603 (who-ps-html (:p
"Creation date of current point. Will be updated when you change this point."))
605 (who-ps-html (:p
"Creator of current point. Will be updated when you change this point."))
606 :remove-work-layers-button
607 (who-ps-html (:p
"Discard the current, unstored point but let the rest of the workspace untouched."))
609 (who-ps-html (:p
"View some info about phoros."))
611 (who-ps-html (:p
"Finish this session. Fresh login is required to continue."))
613 (who-ps-html (:p
"Clicking into the streetmap fetches images which most probably feature the clicked point.")
614 (:p
"TODO: This is not quite so. Currently images taken from points nearest to the clicked one are displayed.")
615 (:p
"To pan the map, drag the mouse. To zoom, spin the mouse wheel or hold shift down whilst dragging a box."))
617 (who-ps-html (:p
"Clicking into an image sets or resets the active point there. Once a feature is marked by active points in more than one image, the estimated position is calculated.")
618 (:p
"To pan an image, drag the mouse. To zoom, spin the mouse wheel or hold shift down whilst dragging a box."))
619 ol-Control-Pan-West-Item-Inactive
620 (who-ps-html (:p
"Move viewport left."))
621 ol-Control-Pan-East-Item-Inactive
622 (who-ps-html (:p
"Move viewport right."))
623 ol-Control-Pan-North-Item-Inactive
624 (who-ps-html (:p
"Move viewport up."))
625 ol-Control-Pan-South-Item-Inactive
626 (who-ps-html (:p
"Move viewport down."))
627 ol-Control-Zoom-In-Item-Inactive
628 (who-ps-html (:p
"Zoom in."))
629 ol-Control-Zoom-Out-Item-Inactive
630 (who-ps-html (:p
"Zoom out."))
631 streetmap-Zoom-To-Max-Extent-Item-Inactive
632 (who-ps-html (:p
"Zoom to the extent of presentation project."))
633 ol-Control-Zoom-To-Max-Extent-Item-Inactive
634 (who-ps-html (:p
"Zoom out completely, restoring the original view."))
635 :image-layer-switcher
636 (who-ps-html (:p
"Toggle display of image."))
637 :streetmap-layer-switcher
638 (who-ps-html (:p
"Toggle visibility of data layers, or choose a background streetmap. (TODO: currently only one \"choice\")"))
640 (who-ps-html (:p
"Click to re-center streetmap, or drag the red rectangle."))
641 :streetmap-mouse-position
642 (who-ps-html (:p
"Position in geographic coordinates when cursor is in streetmap."))
644 (who-ps-html (:p
"Hints on Phoros' displays and controls are shown here while hovering over the respective elements."))))
646 (defun add-help-topic (topic element
)
647 "Add mouse events to DOM element that initiate display of a
650 (setf (@ element onmouseover
)
652 (lambda () (show-help x
)))
654 (setf (@ element onmouseout
) show-help
)))
656 (defun add-help-events ()
657 "Add mouse events to DOM elements that initiate display of a
660 (topic *help-topics
*)
661 (add-help-topic topic
(chain document
(get-element-by-id topic
)))
662 (dolist (element (chain document
(get-elements-by-class-name topic
)))
663 (add-help-topic topic element
))))
665 (defun show-help (&optional topic
)
666 "Put text on topic into help-display"
667 (setf (inner-html-with-id "help-display")
668 (let ((help-body (getprop *help-topics
* topic
)))
669 (if (undefined help-body
)
673 (defvar *click-control
*
677 (@ *open-layers
*control
)
679 :default-handler-options
688 (@ this handler-options
)
693 (@ this default-handler-options
))))
698 (apply this arguments
))
699 (setf (@ this handler
)
700 (new (chain *open-layers
704 :click
(@ this trigger
))
705 (@ this handler-options
))))))))))
708 (new (chain *open-layers
(*projection
"EPSG:4326"))))
709 (defvar +spherical-mercator
+
710 (new (chain *open-layers
(*projection
"EPSG:900913"))))
712 (defvar +user-name
+ (lisp (session-value 'user-name
))
713 "User's (short) name")
714 (defvar +user-role
+ (lisp (string-downcase (session-value 'user-role
)))
715 "User's permissions")
717 (defvar +presentation-project-bounds
+
718 (chain (new (chain *open-layers
721 (lisp (session-value 'presentation-project-bbox
)))))
722 (transform +geographic
+ +spherical-mercator
+))
723 "Bounding box of the entire presentation project.")
725 (defvar *images
* (array) "Collection of the photos currently shown.")
726 (defvar *streetmap
* undefined
727 "The streetmap shown to the user.")
728 (defvar *streetmap-estimated-position-layer
*)
729 (defvar *point-attributes-select
* undefined
730 "The HTML element for selecting user point attributes.")
732 (defvar *global-position
*
733 "Coordinates of the current estimated position")
735 (defvar *bbox-strategy
* (chain *open-layers
*strategy
*bbox
*))
736 (setf (chain *bbox-strategy
* prototype ratio
) 1.5)
737 (setf (chain *bbox-strategy
* prototype res-factor
) 1.5)
739 (defvar *json-parser
* (new (chain *open-layers
*format
*json
*)))
741 (defvar *geojson-format
* (chain *open-layers
*format
*geo-j-s-o-n
))
742 (setf (chain *geojson-format
* prototype ignore-extra-dims
) t
) ;doesn't handle height anyway
743 (setf (chain *geojson-format
* prototype external-projection
) +geographic
+)
744 (setf (chain *geojson-format
* prototype internal-projection
) +geographic
+)
746 (defvar *http-protocol
* (chain *open-layers
*protocol
*http
*))
747 (setf (chain *http-protocol
* prototype format
) (new *geojson-format
*))
749 (defvar *survey-layer
*
755 strategies
(array (new (*bbox-strategy
*)))
757 (new (*http-protocol
*
758 (create :url
"/phoros-lib/points"))))))))
760 (defvar *user-point-layer
*
766 strategies
(array (new *bbox-strategy
*))
768 (new (*http-protocol
*
769 (create :url
"/phoros-lib/user-points"))))))))
771 (defvar *pristine-images-p
* t
772 "T if none of the current images has been clicked into yet.")
774 (defvar *current-user-point
* undefined
775 "The currently selected user-point.")
777 (defvar *user-points-select-control
*
778 (new (chain *open-layers
*control
(*select-feature
*user-point-layer
*))))
779 ;;(defvar google (new ((@ *open-layers *Layer *google) "Google Streets")))
780 (defvar *osm-layer
* (new (chain *open-layers
*layer
(*osm
*))))
781 (defvar *click-streetmap
*
782 (new (*click-control
* (create :trigger request-photos
))))
784 (defun write-permission-p (&optional
(current-owner +user-name
+))
785 "Nil if current user can't edit stuff created by current-owner or, without arguments, new stuff."
786 (or (== +user-role
+ "admin")
787 (and (== +user-role
+ "write")
788 (== +user-name
+ current-owner
))))
791 "Anything necessary to deal with a photo."
792 (setf (getprop this
'map
)
793 (new ((getprop *open-layers
'*map
)
794 (create projection
+spherical-mercator
+
796 controls
(array (new (chain *open-layers
799 (setf (getprop this
'dummy
) false
) ;TODO why? (omitting splices map components directly into *image)
802 (setf (getprop *image
'prototype
'show-photo
) show-photo
)
803 (setf (getprop *image
'prototype
'draw-epipolar-line
) draw-epipolar-line
)
804 (setf (getprop *image
'prototype
'draw-active-point
) draw-active-point
)
805 (setf (getprop *image
'prototype
'draw-estimated-positions
)
806 draw-estimated-positions
)
808 (defun photo-path (photo-parameters)
809 "Create from stuff found in photo-parameters a path for use in
811 (+ "/phoros-lib/photo/" (@ photo-parameters directory
) "/"
812 (@ photo-parameters filename
) "/"
813 (@ photo-parameters byte-position
) ".png"))
815 (defun has-layer-p (map layer-name
)
816 "False if map doesn't have a layer called layer-name."
817 (chain map
(get-layers-by-name layer-name
) length
))
819 (defun some-active-point-p ()
820 "False if no image in *images* has an Active Point."
822 for i across
*images
*
823 sum
(has-layer-p (getprop i
'map
) "Active Point")))
825 (defun remove-layer (map layer-name
)
826 "Destroy layer layer-name in map."
827 (when (has-layer-p map layer-name
)
828 (chain map
(get-layers-by-name layer-name
) 0 (destroy))))
830 (defun remove-any-layers (layer-name)
831 "Destroy in all *images* and in *streetmap* the layer named layer-name."
833 for i across
*images
* do
(remove-layer (getprop i
'map
) layer-name
))
834 (remove-layer *streetmap
* layer-name
))
836 (defun reset-controls ()
837 "Destroy user-generated layers in *streetmap* and in all *images*."
838 (disable-element-with-id "finish-point-button")
839 (disable-element-with-id "delete-point-button")
840 (disable-element-with-id "remove-work-layers-button")
841 (setf (inner-html-with-id "h2-controls") "Create Point")
842 (setf (inner-html-with-id "creator") nil
)
843 (setf (inner-html-with-id "point-creation-date") nil
))
845 (defun reset-layers-and-controls ()
846 (remove-any-layers "Epipolar Line")
847 (remove-any-layers "Active Point")
848 (remove-any-layers "Estimated Position")
849 (remove-any-layers "User Point")
850 (when (and (!= undefined
*current-user-point
*)
851 (chain *current-user-point
* layer
))
852 (chain *user-points-select-control
* (unselect *current-user-point
*)))
854 (setf *pristine-images-p
* t
)
857 (defun enable-element-with-id (id)
858 "Activate HTML element with id=\"id\"."
859 (setf (chain document
(get-element-by-id id
) disabled
) nil
))
861 (defun disable-element-with-id (id)
862 "Grey out HTML element with id=\"id\"."
863 (setf (chain document
(get-element-by-id id
) disabled
) t
))
865 (defmacro value-with-id
(id)
866 "Value of element with id=\"id\"."
867 `(chain document
(get-element-by-id ,id
) value
))
869 (defun refresh-layer (layer)
870 "Have layer re-request and redraw features."
871 (chain layer
(refresh (create :force t
))))
873 (defun present-photos ()
874 "Handle the response triggered by request-photos."
875 (let ((photo-parameters
877 (read (@ photo-request-response response-text
)))))
879 for p across photo-parameters
880 for i across
*images
*
882 (setf (getprop i
'photo-parameters
) p
)
883 ((getprop i
'show-photo
)))
884 ;; (setf (@ (aref photo-parameters 0) angle180) 1) ; Debug: coordinate flipping
887 (defun request-photos (event)
888 "Handle the response to a click into *streetmap*; fetch photo data."
889 (disable-element-with-id "finish-point-button")
890 (disable-element-with-id "remove-work-layers-button")
891 (remove-any-layers "Estimated Position")
893 ((@ ((@ *streetmap
* get-lon-lat-from-pixel
) (@ event xy
)) transform
)
894 +spherical-mercator
+ ; why?
899 (create :longitude
(@ lonlat lon
)
900 :latitude
(@ lonlat lat
)
901 :zoom
((@ *streetmap
* get-zoom
))
902 :count
(lisp *number-of-images
*))))))
903 (setf photo-request-response
904 ((@ *open-layers
*Request
*POST
*)
905 (create :url
"/phoros-lib/local-data"
907 :headers
(create "Content-type" "text/plain"
908 "Content-length" (@ content length
))
909 :success present-photos
)))))
911 (defun draw-epipolar-line ()
912 "Draw an epipolar line from response triggered by clicking
913 into a (first) photo."
914 (enable-element-with-id "remove-work-layers-button")
915 (let* ((epipolar-line
918 (@ this epipolar-request-response response-text
))))
922 (new ((@ *open-layers
*geometry
*point
)
923 (@ x
:m
) (@ x
:n
)))))))
925 (new (chain *open-layers
931 (*line-string points
))))))))
932 (setf (chain feature render-intent
) "temporary")
933 (chain this epipolar-layer
934 (add-features feature
))))
935 ;; either *line-string or *multi-point are usable
937 (defun draw-estimated-positions ()
938 "Draw into streetmap and into all images points at Estimated
939 Position. Estimated Position is the point returned so far from
940 photogrammetric calculations that are triggered by clicking into
942 (when (write-permission-p)
943 (setf (chain document
944 (get-element-by-id "finish-point-button")
947 (enable-element-with-id "finish-point-button"))
948 (let* ((estimated-positions-request-response
952 'estimated-positions-request-response
955 (aref estimated-positions-request-response
1)))
956 (setf *global-position
*
957 (aref estimated-positions-request-response
0))
959 (new ((@ *open-layers
*feature
*vector
)
960 ((@ (new ((@ *open-layers
*geometry
*point
)
961 (getprop *global-position
* 'longitude
)
962 (getprop *global-position
* 'latitude
)))
963 transform
) +geographic
+ +spherical-mercator
+)))))
964 (setf (chain feature render-intent
) "temporary")
965 (setf *streetmap-estimated-position-layer
*
966 (new (chain *open-layers
968 (*vector
"Estimated Position"
969 (create display-in-layer-switcher nil
)))))
970 (chain *streetmap-estimated-position-layer
*
971 (add-features feature
))
972 (chain *streetmap
* (add-layer *streetmap-estimated-position-layer
*)))
973 (let ((estimated-position-style
974 (create stroke-color
(chain *open-layers
*feature
*vector
975 style
"temporary" stroke-color
)
980 for p in estimated-positions
982 (when i
;otherwise a photogrammetry error has occured
983 (setf (@ i estimated-position-layer
)
985 (chain *open-layers
*layer
986 (*vector
"Estimated Position"
987 (create display-in-layer-switcher nil
)))))
988 (setf (chain i estimated-position-layer style
)
989 estimated-position-style
)
992 (chain *open-layers
*geometry
(*point
997 (chain *open-layers
*feature
(*vector point
)))))
999 (add-layer (@ i estimated-position-layer
)))
1000 (chain i estimated-position-layer
1001 (add-features feature
))))))))
1003 (defun draw-user-point ()
1004 "Draw currently selected user point into all images."
1005 (let* ((user-point-in-images
1006 (chain *json-parser
*
1008 (getprop *user-point-in-images-response
*
1012 for p in user-point-in-images
1014 (when i
;otherwise a photogrammetry error has occured
1015 (setf (@ i user-point-layer
)
1016 (new (chain *open-layers
1018 (*vector
"User Point"
1019 (create display-in-layer-switcher nil
)))))
1021 (new (chain *open-layers
*geometry
(*point
1025 (new (chain *open-layers
*feature
(*vector point
)))))
1026 (setf (chain feature render-intent
) "select")
1027 (chain i map
(add-layer (@ i user-point-layer
)))
1028 (chain i user-point-layer
(add-features feature
)))))))
1030 (defun finish-point ()
1031 "Send current *global-position* as a user point to the database."
1032 (let ((global-position-etc *global-position
*))
1033 (setf (chain global-position-etc attribute
)
1035 (elt (chain *point-attributes-select
* options
)
1036 (chain *point-attributes-select
* options selected-index
))
1038 (setf (chain global-position-etc description
)
1039 (value-with-id "point-description"))
1040 (setf (chain global-position-etc numeric-description
)
1041 (value-with-id "point-numeric-description"))
1043 (chain *json-parser
*
1044 (write global-position-etc
))))
1045 ((@ *open-layers
*Request
*POST
*)
1046 (create :url
"/phoros-lib/store-point"
1048 :headers
(create "Content-type" "text/plain"
1049 "Content-length" (@ content length
))
1051 (refresh-layer *user-point-layer
*)
1052 (reset-layers-and-controls)))))
1053 (let* ((previous-numeric-description ;increment if possible
1054 (chain global-position-etc numeric-description
))
1055 (current-numeric-description
1056 (1+ (parse-int previous-numeric-description
10))))
1057 (setf (value-with-id "point-numeric-description")
1058 (if (is-finite current-numeric-description
)
1059 current-numeric-description
1060 previous-numeric-description
)))))
1062 (defun update-point ()
1063 "Send changes to currently selected user point to database."
1065 (create user-point-id
(chain *current-user-point
* fid
)
1068 (elt (chain *point-attributes-select
* options
)
1069 (chain *point-attributes-select
* options selected-index
))
1072 (value-with-id "point-description")
1074 (value-with-id "point-numeric-description")))
1076 (chain *json-parser
*
1077 (write point-data
))))
1078 ((@ *open-layers
*Request
*POST
*)
1079 (create :url
"/phoros-lib/update-point"
1081 :headers
(create "Content-type" "text/plain"
1082 "Content-length" (@ content length
))
1084 (refresh-layer *user-point-layer
*)
1085 (reset-layers-and-controls))))))
1087 (defun delete-point ()
1088 "Purge currently selected user point from database."
1089 (let ((user-point-id (chain *current-user-point
* fid
)))
1091 (chain *json-parser
*
1092 (write user-point-id
)))
1093 ((@ *open-layers
*Request
*POST
*)
1094 (create :url
"/phoros-lib/delete-point"
1096 :headers
(create "Content-type" "text/plain"
1097 "Content-length" (@ content length
))
1099 (refresh-layer *user-point-layer
*)
1100 (reset-layers-and-controls))))))
1102 (defun draw-active-point ()
1103 "Draw an Active Point, i.e. a point used in subsequent
1104 photogrammetric calculations."
1105 (chain this active-point-layer
1107 (new ((@ *open-layers
*feature
*vector
)
1108 (new ((@ *open-layers
*geometry
*point
)
1109 (getprop this
'photo-parameters
'm
)
1110 (getprop this
'photo-parameters
'n
))))))))
1112 (defun image-click-action (clicked-image)
1114 "Do appropriate things when an image is clicked into."
1116 ((@ (@ clicked-image map
) get-lon-lat-from-view-port-px
)
1119 (getprop clicked-image
'photo-parameters
))
1120 pristine-image-p content request
)
1121 (setf (@ photo-parameters m
) (@ lonlat lon
)
1122 (@ photo-parameters n
) (@ lonlat lat
))
1123 (remove-layer (getprop clicked-image
'map
) "Active Point")
1124 (remove-any-layers "Epipolar Line")
1125 (setf *pristine-images-p
* (not (some-active-point-p)))
1126 (setf (@ clicked-image active-point-layer
)
1127 (new (chain *open-layers
1129 (*vector
"Active Point"
1130 (create display-in-layer-switcher nil
)))))
1131 ((@ clicked-image map add-layer
)
1132 (@ clicked-image active-point-layer
))
1133 ((getprop clicked-image
'draw-active-point
))
1138 (remove-any-layers "User Point") ;from images
1139 (when (and (!= undefined
*current-user-point
*)
1140 (chain *current-user-point
* layer
))
1141 (chain *user-points-select-control
* (unselect *current-user-point
*)))
1143 for i across
*images
* do
1144 (unless (== i clicked-image
)
1146 (@ i epipolar-layer
)
1147 (new (chain *open-layers
1149 (*vector
"Epipolar Line"
1150 (create display-in-layer-switcher nil
))))
1151 content
(chain *json-parser
*
1153 (append (array photo-parameters
)
1154 (@ i photo-parameters
))))
1155 (@ i epipolar-request-response
)
1156 ((@ *open-layers
*Request
*POST
*)
1157 (create :url
"/phoros-lib/epipolar-line"
1159 :headers
(create "Content-type" "text/plain"
1162 :success
(getprop i
'draw-epipolar-line
)
1164 ((@ i map add-layer
) (@ i epipolar-layer
)))))
1166 (remove-any-layers "Epipolar Line")
1167 (remove-any-layers "Estimated Position")
1168 (let* ((active-pointed-photo-parameters
1170 for i across
*images
*
1171 when
(has-layer-p (getprop i
'map
) "Active Point")
1172 collect
(getprop i
'photo-parameters
)))
1174 (chain *json-parser
*
1176 (list active-pointed-photo-parameters
1180 x
'photo-parameters
)))))))))
1181 (setf (@ clicked-image estimated-positions-request-response
)
1182 ((@ *open-layers
*Request
*POST
*)
1183 (create :url
"/phoros-lib/estimated-positions"
1185 :headers
(create "Content-type" "text/plain"
1188 :success
(getprop clicked-image
1189 'draw-estimated-positions
)
1190 :scope clicked-image
)))))))))
1192 (defun show-photo ()
1193 "Show the photo described in this object's photo-parameters."
1195 repeat
((getprop this
'map
'get-num-layers
))
1196 do
((getprop this
'map
'layers
0 'destroy
)))
1197 ((getprop this
'map
'add-layer
)
1198 (new ((@ *open-layers
*layer
*image
)
1200 (photo-path (getprop this
'photo-parameters
))
1201 (new ((@ *open-layers
*bounds
) -
.5 -
.5
1202 (+ (getprop this
'photo-parameters
'sensor-width-pix
)
1204 (+ (getprop this
'photo-parameters
'sensor-height-pix
)
1205 .5))) ; coordinates shown
1206 (new ((@ *open-layers
*size
) 512 256))
1208 (chain this map
(zoom-to-max-extent)))
1210 (defun initialize-image (image-index)
1211 "Create an image usable for displaying photos at position
1212 image-index in array *images*."
1213 (setf (aref *images
* image-index
) (new *image
))
1214 (setf (@ (aref *images
* image-index
) image-click-action
)
1215 (image-click-action (aref *images
* image-index
)))
1216 (setf (@ (aref *images
* image-index
) click
)
1217 (new (*click-control
*
1218 (create :trigger
(@ (aref *images
* image-index
)
1219 image-click-action
)))))
1220 (chain (aref *images
* image-index
)
1223 (@ (aref *images
* image-index
) click
)))
1224 (chain (aref *images
* image-index
) click
(activate))
1225 ;;(chain (aref *images* image-index)
1228 ;; (new (chain *open-layers
1234 ;; (get-element-by-id
1235 ;; (+ "image-" image-index "-zoom")))))))))
1236 (chain (aref *images
* image-index
)
1239 (new (chain *open-layers
1246 (+ "image-" image-index
"-layer-switcher")))
1247 rounded-corner nil
))))))
1248 (let ((pan-west-control
1249 (new (chain *open-layers
*control
(*pan
"West"))))
1251 (new (chain *open-layers
*control
(*pan
"North"))))
1253 (new (chain *open-layers
*control
(*pan
"South"))))
1255 (new (chain *open-layers
*control
(*pan
"East"))))
1257 (new (chain *open-layers
*control
(*zoom-in
))))
1259 (new (chain *open-layers
*control
(*zoom-out
))))
1260 (zoom-to-max-extent-control
1261 (new (chain *open-layers
*control
(*zoom-to-max-extent
))))
1263 (new (chain *open-layers
1270 (+ "image-" image-index
"-zoom")))))))))
1271 (chain (aref *images
* image-index
) map
(add-control pan-zoom-panel
))
1272 (chain pan-zoom-panel
(add-controls (array pan-west-control
1278 zoom-to-max-extent-control
))))
1279 (chain (aref *images
* image-index
)
1281 (render (chain document
1283 (+ "image-" image-index
))))))
1285 (defun user-point-selected (event)
1286 (setf *current-user-point
* (chain event feature
))
1287 (remove-any-layers "Active Point")
1288 (remove-any-layers "Epipolar Line")
1289 (remove-any-layers "Estimated Position")
1290 (remove-any-layers "User Point")
1291 (if (write-permission-p (chain event feature attributes user-name
))
1293 (setf (chain document
(get-element-by-id "finish-point-button") onclick
) update-point
)
1294 (enable-element-with-id "finish-point-button")
1295 (enable-element-with-id "delete-point-button")
1296 (setf (inner-html-with-id "h2-controls") "Edit Point"))
1298 (disable-element-with-id "finish-point-button")
1299 (disable-element-with-id "delete-point-button")
1300 (setf (inner-html-with-id "h2-controls") "View Point")))
1301 (setf (inner-html-with-id "creator")
1302 (+ "(by " (chain event feature attributes user-name
) ")"))
1303 (setf (value-with-id "point-attribute") (chain event feature attributes attribute
))
1304 (setf (value-with-id "point-description") (chain event feature attributes description
))
1305 (setf (value-with-id "point-numeric-description") (chain event feature attributes numeric-description
))
1306 (setf (inner-html-with-id "point-creation-date") (chain event feature attributes creation-date
))
1308 (chain *json-parser
*
1310 (array (chain event feature fid
)
1312 for i across
*images
*
1313 collect
(chain i photo-parameters
))))))
1314 (setf *user-point-in-images-response
*
1315 ((@ *open-layers
*Request
*POST
*)
1316 (create :url
"/phoros-lib/user-point-positions"
1318 :headers
(create "Content-type" "text/plain"
1319 "Content-length" (@ content length
))
1320 :success draw-user-point
))))
1323 "Prepare user's playground."
1324 (when (write-permission-p)
1325 (enable-element-with-id "point-attribute")
1326 (enable-element-with-id "point-description")
1327 (enable-element-with-id "point-numeric-description")
1328 (setf (inner-html-with-id "h2-controls") "Create Point"))
1329 (setf *point-attributes-select
* (chain document
(get-element-by-id "point-attribute")))
1331 (loop for i in
'("solitary" "polyline" "polygon") do
1332 (setf point-attribute-item
(chain document
(create-element "option")))
1333 (setf (chain point-attribute-item text
) i
)
1334 (chain *point-attributes-select
* (add point-attribute-item null
))) ;TODO: input of user-defined attributes
1339 (create projection
+geographic
+
1340 display-projection
+geographic
+
1341 controls
(array (new (chain *open-layers
1344 (new (chain *open-layers
1346 (*attribution
)))))))))
1351 (new (chain *open-layers
1358 "streetmap-layer-switcher"))
1359 rounded-corner nil
))))))
1360 (let ((pan-west-control
1361 (new (chain *open-layers
*control
(*pan
"West"))))
1363 (new (chain *open-layers
*control
(*pan
"North"))))
1365 (new (chain *open-layers
*control
(*pan
"South"))))
1367 (new (chain *open-layers
*control
(*pan
"East"))))
1369 (new (chain *open-layers
*control
(*zoom-in
))))
1371 (new (chain *open-layers
*control
(*zoom-out
))))
1372 (zoom-to-max-extent-control
1378 display-class
"streetmapZoomToMaxExtent"
1382 +presentation-project-bounds
+ ))))))))
1384 (new (chain *open-layers
1391 "streetmap-zoom")))))))
1393 (new (chain *open-layers
1401 "streetmap-overview")))))))
1402 (mouse-position-control
1403 (new (chain *open-layers
1406 (create div
(chain document
1408 "streetmap-mouse-position"))
1409 empty-string
"longitude, latitude"))))))
1410 (chain *streetmap
* (add-control pan-zoom-panel
))
1411 (chain pan-zoom-panel
1412 (add-controls (array pan-west-control
1418 zoom-to-max-extent-control
)))
1419 (chain *streetmap
* (add-control *click-streetmap
*))
1420 (chain *click-streetmap
* (activate))
1422 (chain *user-point-layer
*
1424 (register "featureselected"
1425 *user-point-layer
* user-point-selected
))
1426 (chain *user-point-layer
*
1428 (register "featureunselected"
1429 *user-point-layer
* reset-controls
))
1430 (chain *streetmap
* (add-control *user-points-select-control
*))
1431 (chain *user-points-select-control
* (activate))
1433 (chain *streetmap
* (add-layer *osm-layer
*))
1434 ;;(chain *streetmap* (add-layer *google*))
1435 (chain *streetmap
* (add-layer *survey-layer
*))
1436 (chain *streetmap
* (add-layer *user-point-layer
*))
1437 (setf (chain overview-map element
)
1438 (chain document
(get-element-by-id
1439 "streetmap-overview-element")))
1440 (chain *streetmap
* (add-control overview-map
))
1442 (zoom-to-extent +presentation-project-bounds
+))
1443 (chain *streetmap
* (add-control mouse-position-control
)))
1445 for i from
0 to
(lisp (1- *number-of-images
*))
1446 do
(initialize-image i
))
1447 (add-help-events)))))
1449 (define-easy-handler
1450 (view :uri
"/phoros-lib/view" :default-request-type
:post
) ()
1451 "Serve the client their main workspace."
1453 (session-value 'authenticated-p
)
1454 (who:with-html-output-to-string
(s nil
:indent t
)
1456 :xmlns
"http://www.w3.org/1999/xhtml"
1461 "Phoros: " (session-value 'presentation-project-name
))))
1462 (if *use-multi-file-openlayers
*
1464 (:script
:src
"/phoros-lib/openlayers/lib/Firebug/firebug.js")
1465 (:script
:src
"/phoros-lib/openlayers/lib/OpenLayers.js")
1466 ;;(:script :src "/phoros-lib/openlayers/lib/proj4js.js") ;TODO: we don't seem to use this
1468 (who:htm
(:script
:src
"/phoros-lib/ol/OpenLayers.js")))
1469 (:link
:rel
"stylesheet" :href
"/phoros-lib/css/style.css" :type
"text/css")
1470 (:script
:src
"/phoros-lib/phoros.js")
1471 ;;(:script :src "http://maps.google.com/maps/api/js?sensor=false")
1476 "Phoros: " (who:str
(session-value 'user-full-name
))
1477 (who:fmt
" (~A)" (session-value 'user-name
))
1478 "with " (:span
:id
"user-role"
1479 (who:str
(session-value 'user-role
)))
1481 (:span
:id
"presentation-project-name"
1482 (who:str
(session-value 'presentation-project-name
))))
1483 (:div
:class
"controlled-streetmap"
1484 (:div
:id
"streetmap-controls" :class
"streetmap-controls"
1485 (:div
:class
"streetmap-zoom-and-layer-switcher"
1486 (:div
:id
"streetmap-layer-switcher"
1487 :class
"streetmap-layer-switcher")
1488 (:div
:id
"streetmap-zoom" :class
"streetmap-zoom"))
1489 (:div
:id
"streetmap-overview" :class
"streetmap-overview")
1490 (:div
:id
"streetmap-empty-space" :class
"streetmap-empty-space")
1491 (:div
:id
"streetmap-mouse-position" :class
"streetmap-mouse-position"))
1492 (:div
:id
"streetmap" :class
"smallmap" :style
"cursor:crosshair"))
1493 (:div
:class
"phoros-controls"
1494 (:button
:id
"blurb-button"
1496 :onclick
"self.location.href = \"/phoros-lib/blurb\""
1498 (:button
:id
"logout-button"
1500 :onclick
"self.location.href = \"/phoros-lib/logout\""
1503 (:button
:id
"remove-work-layers-button" :disabled t
1504 :type
"button" :onclick
(ps-inline (reset-layers-and-controls))
1506 (:h2
(:span
:id
"h2-controls") (:span
:id
"creator"))
1507 (:small
(:code
:id
"point-creation-date"))
1509 (:select
:id
"point-attribute" :disabled t
1510 :size
1 :name
"point-attribute")
1511 (:input
:id
"point-numeric-description" :disabled t
1512 :type
"text" :size
6 :name
"point-numeric-description")
1514 (:input
:id
"point-description" :disabled t
1515 :type
"text" :size
20 :name
"point-description")
1517 (:button
:disabled t
:id
"finish-point-button"
1520 (:button
:id
"delete-point-button" :disabled t
1521 :type
"button" :onclick
(ps-inline (delete-point))
1523 (:div
:class
"smalltext"
1524 (:h2
:id
"h2-help" "Help")
1525 (:div
:id
"help-display"))
1526 (:div
:id
"images" :style
"clear:both"
1528 for i from
0 below
*number-of-images
* do
1530 (:div
:class
"controlled-image"
1531 (:div
:id
(format nil
"image-~S-controls" i
)
1532 :class
"image-controls"
1533 (:div
:id
(format nil
"image-~S-zoom" i
)
1534 :class
"image-zoom")
1535 (:div
:id
(format nil
"image-~S-layer-switcher" i
)
1536 :class
"image-layer-switcher"))
1537 (:div
:id
(format nil
"image-~S" i
)
1538 :class
"image" :style
"cursor:crosshair"))))))))
1540 (concatenate 'string
"/phoros/" (session-value 'presentation-project-name
))
1541 :add-session-id t
)))
1543 (define-easy-handler (epipolar-line :uri
"/phoros-lib/epipolar-line") ()
1544 "Receive vector of two sets of picture parameters, respond with
1545 JSON encoded epipolar-lines."
1546 (when (session-value 'authenticated-p
)
1547 (let* ((data (json:decode-json-from-string
(raw-post-data))))
1548 (json:encode-json-to-string
1549 (photogrammetry :epipolar-line
(first data
) (second data
))))))
1551 (define-easy-handler
1552 (estimated-positions :uri
"/phoros-lib/estimated-positions")
1554 "Receive a two-part JSON vector comprising (1) a vector containing
1555 sets of picture-parameters including clicked (\"active\") points
1556 stored in :m, :n; and (2) a vector containing sets of
1557 picture-parameters; respond with a JSON encoded two-part vector
1558 comprising (1) a point in global coordinates; and (2) a vector of
1559 image coordinates (m, n) for the global point that correspond to the
1560 images from the received second vector. TODO: report error on bad
1561 data (ex: points too far apart)."
1562 ;; TODO: global-point-for-display should probably contain a proj string in order to make sense of the (cartesian) standard deviations.
1563 (when (session-value 'authenticated-p
)
1564 (let* ((data (json:decode-json-from-string
(raw-post-data)))
1565 (active-point-photo-parameters (first data
))
1566 (destination-photo-parameters (second data
))
1567 (cartesian-system (cdr (assoc :cartesian-system
(first active-point-photo-parameters
))))
1568 (global-point-cartesian (photogrammetry :multi-position-intersection active-point-photo-parameters
))
1569 (global-point-geographic-radians
1570 (proj:cs2cs
(list (cdr (assoc :x-global global-point-cartesian
))
1571 (cdr (assoc :y-global global-point-cartesian
))
1572 (cdr (assoc :z-global global-point-cartesian
)))
1573 :source-cs cartesian-system
))
1574 (global-point-for-display ;points geographic cs, degrees; std deviations in cartesian cs
1575 (pairlis '(:longitude
:latitude
:ellipsoid-height
1576 :stdx-global
:stdy-global
:stdz-global
)
1578 (proj:radians-to-degrees
(first global-point-geographic-radians
))
1579 (proj:radians-to-degrees
(second global-point-geographic-radians
))
1580 (third global-point-geographic-radians
)
1581 (cdr (assoc :stdx-global global-point-cartesian
))
1582 (cdr (assoc :stdy-global global-point-cartesian
))
1583 (cdr (assoc :stdz-global global-point-cartesian
)))))
1586 for i in destination-photo-parameters
1589 (photogrammetry :reprojection i global-point-cartesian
)))))
1590 (json:encode-json-to-string
1591 (list global-point-for-display image-coordinates
)))))
1593 (define-easy-handler
1594 (user-point-positions :uri
"/phoros-lib/user-point-positions")
1596 "Receive a two-part JSON vector comprising (1) a user-point-id and
1597 \(2) a vector containing sets of picture-parameters; respond with a
1598 JSON encoded vector of image coordinates (m, n) for the global
1599 coordinates of the user point with user-point-id that correspond to
1600 the images from the received image vector."
1601 (when (session-value 'authenticated-p
)
1602 (let* ((user-point-table-name
1603 (user-point-table-name (session-value 'presentation-project-name
)))
1604 (data (json:decode-json-from-string
(raw-post-data)))
1605 (user-point-id (first data
))
1606 (destination-photo-parameters (second data
))
1607 (cartesian-system (cdr (assoc :cartesian-system
(first destination-photo-parameters
)))) ;TODO: in rare cases, coordinate systems of the images shown may differ
1608 (global-point-geographic
1609 (with-connection *postgresql-credentials
*
1613 (:st_x
(:st_transform
'coordinates
*standard-coordinates
*))
1616 (:st_y
(:st_transform
'coordinates
*standard-coordinates
*))
1619 (:st_z
(:st_transform
'coordinates
*standard-coordinates
*))
1621 :from user-point-table-name
1622 :where
(:= 'user-point-id user-point-id
))
1624 (global-point-cartesian
1625 (pairlis '(:x-global
:y-global
:z-global
)
1628 (proj:degrees-to-radians
(first global-point-geographic
))
1629 (proj:degrees-to-radians
(second global-point-geographic
))
1630 (third global-point-geographic
))
1631 :destination-cs cartesian-system
)))
1634 for i in destination-photo-parameters
1637 (photogrammetry :reprojection i global-point-cartesian
)))))
1638 (json:encode-json-to-string
1639 image-coordinates
))))
1641 (define-easy-handler (multi-position-intersection :uri
"/phoros-lib/intersection") ()
1642 "Receive vector of sets of picture parameters, respond with stuff."
1643 (when (session-value 'authenticated-p
)
1644 (let* ((data (json:decode-json-from-string
(raw-post-data))))
1645 (json:encode-json-to-string
(photogrammetry :multi-position-intersection data
)))))
1647 (defgeneric photogrammetry
(mode photo-1
&optional photo-2
)
1648 (:documentation
"Call to photogrammetry library. Dispatch on mode."))
1650 (defmethod photogrammetry :around
(mode clicked-photo
&optional other-photo
)
1651 "Prepare and clean up a run of photogrammetry."
1652 (declare (ignore other-photo
))
1653 (bt:with-lock-held
(*photogrammetry-mutex
*)
1659 (defmethod photogrammetry ((mode (eql :epipolar-line
)) clicked-photo
&optional other-photo
)
1660 "Return in an alist an epipolar line in coordinates of other-photo from m and n in clicked-photo."
1661 (add-cam* clicked-photo
)
1662 (add-bpoint* clicked-photo
)
1663 (add-global-car-reference-point* clicked-photo t
)
1664 (add-cam* other-photo
)
1665 (add-global-car-reference-point* other-photo t
)
1667 for i
= 2d0 then
(* i
1.4) until
(> i
50)
1669 (set-distance-for-epipolar-line i
)
1670 when
(ignore-errors (calculate))
1671 collect
(pairlis '(:m
:n
) (list (flip-m-maybe (get-m) other-photo
)
1672 (flip-n-maybe (get-n) other-photo
)))))
1674 (defmethod photogrammetry ((mode (eql :reprojection
)) photo
&optional global-point
)
1675 "Calculate reprojection from photo."
1677 (add-global-measurement-point* global-point
)
1678 (add-global-car-reference-point* photo
)
1679 (set-global-reference-frame)
1682 (list (flip-m-maybe (get-m) photo
) (flip-n-maybe (get-n) photo
))))
1684 (defmethod photogrammetry ((mode (eql :multi-position-intersection
)) photos
&optional other-photo
)
1685 "Calculate intersection from photos."
1686 (declare (ignore other-photo
))
1687 (set-global-reference-frame)
1693 (add-global-car-reference-point* photo t
))
1695 (pairlis '(:x-global
:y-global
:z-global
1696 :stdx-global
:stdy-global
:stdz-global
)
1698 (get-x-global) (get-y-global) (get-z-global)
1699 (get-stdx-global) (get-stdy-global) (get-stdz-global))))
1701 (defmethod photogrammetry ((mode (eql :intersection
)) photo
&optional other-photo
)
1702 "Calculate intersection from two photos that are taken out of the
1703 same local coordinate system. (Used for debugging only)."
1706 (add-cam* other-photo
)
1707 (add-bpoint* other-photo
)
1709 (pairlis '(:x-local
:y-local
:z-local
1710 :stdx-local
:stdy-local
:stdz-local
)
1712 (get-x-local) (get-y-local) (get-z-local)
1713 (get-stdx-local) (get-stdy-local) (get-stdz-local)
1714 (get-x-global) (get-y-global) (get-z-global))))
1716 (defmethod photogrammetry ((mode (eql :mono
)) photo
&optional floor
)
1717 "Return in an alist the intersection point of the ray through m and n in photo, and floor."
1720 (add-ref-ground-surface* floor
)
1721 (add-global-car-reference-point* photo
)
1722 (set-global-reference-frame)
1724 (pairlis '(:x-global
:y-global
:z-global
)
1726 (get-x-global) (get-y-global) (get-z-global))))
1728 (defun flip-m-maybe (m photo
)
1729 "Flip coordinate m when :mounting-angle in photo suggests it necessary."
1730 (if (= 180 (cdr (assoc :mounting-angle photo
)))
1731 (- (cdr (assoc :sensor-width-pix photo
)) m
)
1733 (defun flip-n-maybe (n photo
)
1734 "Flip coordinate n when :mounting-angle in photo suggests it necessary."
1735 (if (zerop (cdr (assoc :mounting-angle photo
)))
1736 (- (cdr (assoc :sensor-height-pix photo
)) n
)
1739 (defun photogrammetry-arglist (alist &rest keys
)
1740 "Construct an arglist from alist values corresponding to keys."
1741 (mapcar #'(lambda (x) (cdr (assoc x alist
))) keys
))
1743 (defun add-cam* (photo-alist)
1744 "Call add-cam with arguments taken from photo-alist."
1746 (photogrammetry-arglist
1747 photo-alist
:sensor-height-pix
:sensor-width-pix
))
1749 (mapcar #'(lambda (x) (coerce x
'double-float
))
1750 (photogrammetry-arglist photo-alist
1752 :dx
:dy
:dz
:omega
:phi
:kappa
1754 :a-1
:a-2
:a-3
:b-1
:b-2
:c-1
:c-2
:r-0
1755 :b-dx
:b-dy
:b-dz
:b-ddx
:b-ddy
:b-ddz
1756 :b-rotx
:b-roty
:b-rotz
1757 :b-drotx
:b-droty
:b-drotz
))))
1758 (apply #'add-cam
(nconc integer-args double-float-args
))))
1760 (defun add-bpoint* (photo-alist)
1761 "Call add-bpoint with arguments taken from photo-alist."
1762 (add-bpoint (coerce (flip-m-maybe (cdr (assoc :m photo-alist
)) photo-alist
) 'double-float
)
1763 (coerce (flip-n-maybe (cdr (assoc :n photo-alist
)) photo-alist
) 'double-float
)))
1765 (defun add-ref-ground-surface* (floor-alist)
1766 "Call add-ref-ground-surface with arguments taken from floor-alist."
1767 (let ((double-float-args
1768 (mapcar #'(lambda (x) (coerce x
'double-float
))
1769 (photogrammetry-arglist floor-alist
1771 (apply #'add-ref-ground-surface double-float-args
)))
1773 (defun add-global-car-reference-point* (photo-alist &optional cam-set-global-p
)
1774 "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."
1775 (let* ((longitude-radians (proj:degrees-to-radians
(car (photogrammetry-arglist photo-alist
:longitude
))))
1776 (latitude-radians (proj:degrees-to-radians
(car (photogrammetry-arglist photo-alist
:latitude
))))
1777 (ellipsoid-height (car (photogrammetry-arglist photo-alist
:ellipsoid-height
)))
1778 (destination-cs (car (photogrammetry-arglist photo-alist
:cartesian-system
)))
1779 (cartesian-coordinates
1780 (proj:cs2cs
(list longitude-radians latitude-radians ellipsoid-height
)
1781 :destination-cs destination-cs
))
1783 (mapcar #'(lambda (x) (coerce x
'double-float
))
1784 (photogrammetry-arglist photo-alist
1785 :roll
:pitch
:heading
1786 :latitude
:longitude
)))
1788 (nconc cartesian-coordinates other-args
)))
1789 (apply (if cam-set-global-p
1790 #'add-global-car-reference-point-cam-set-global
1791 #'add-global-car-reference-point
)
1792 double-float-args
)))
1794 (defun add-global-measurement-point* (point)
1795 "Call add-global-measurement-point with arguments taken from point."
1796 (let ((double-float-args
1797 (mapcar #'(lambda (x) (coerce x
'double-float
))
1798 (photogrammetry-arglist point
1799 :x-global
:y-global
:z-global
))))
1800 (apply #'add-global-measurement-point double-float-args
)))