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 *spherical-mercator
* 900913
35 "EPSG code of the coordinate system used for some distance calculations.")
37 (defvar *postgresql-credentials
* nil
38 "A list: (database user password host &key (port 5432) use-ssl).")
40 (defvar *postgresql-aux-credentials
* nil
41 "A list: (database user password host &key (port 5432) use-ssl).")
43 (defparameter *photogrammetry-mutex
* (bt:make-lock
"photogrammetry"))
45 (setf *read-default-float-format
* 'double-float
)
47 (defparameter *phoros-server
* nil
"Hunchentoot acceptor.")
49 (defparameter *common-root
* nil
50 "Root directory; contains directories of measuring data.")
52 (defparameter *login-intro
* nil
53 "A few friendly words to be shown below the login form.")
55 (defparameter *postgresql-warnings
* nil
56 "If t, show PostgreSQL's WARNINGs and NOTICEs.")
58 (defparameter *render-footprints-p
* nil
59 "If t, put image footprints into images on client.")
61 (defparameter *use-multi-file-openlayers
* nil
62 "If t, use OpenLayers uncompiled from openlayers/*, which makes
63 debugging easier. Otherwise use a single-file shrunk
66 (defparameter *number-of-images
* 4
67 "Number of photos shown to the HTTP client.")
69 (defparameter *number-of-features-per-layer
* 500
70 "What we think a browser can swallow.")
72 (defparameter *number-of-points-per-aux-linestring
* 500
73 "What we think a browser can swallow.")
75 (defparameter *user-point-creation-date-format
* "IYYY-MM-DD HH24:MI:SS TZ"
76 "SQL date format used for display and GeoJSON export of user points.")
78 (defun phoros-version (&key major minor revision
)
79 "Return version of this program, either one integer part as denoted by
80 the key argument, or the whole dotted string."
81 (let* ((version-string
82 (handler-bind ((warning #'ignore-warnings
))
83 (asdf:component-version
(asdf:find-system
:phoros
))))
85 (mapcar #'parse-integer
86 (cl-utilities:split-sequence
#\. version-string
))))
87 (cond (major (first version-components
))
88 (minor (second version-components
))
89 (revision (third version-components
))
92 (defun check-dependencies ()
93 "Say OK if the necessary external dependencies are available."
96 (geographic-to-utm 33 13 52) ;check cs2cs
97 (phoros-photogrammetry:del-all
) ;check photogrammetry
98 (initialize-leap-seconds) ;check source of leap second info
99 (format *error-output
* "~&OK~%"))
100 (error (e) (format *error-output
* "~A~&" e
))))
102 (defun muffle-postgresql-warnings ()
103 "For current DB, silence PostgreSQL's warnings about implicitly
105 (unless *postgresql-warnings
*
106 (execute "SET client_min_messages TO ERROR;")))
108 (defun check-db (db-credentials)
109 "Check postgresql connection. Return t if successful; show error on
110 *error-output* otherwise. db-credentials is a list like so: (database
111 user password host &key (port 5432) use-ssl)."
114 (setf connection
(apply #'connect db-credentials
))
115 (error (e) (format *error-output
* "Database connection ~S failed: ~A~&"
118 (disconnect connection
)
121 (defun ignore-warnings (c) (declare (ignore c
)) (muffle-warning))
123 (defmethod hunchentoot:session-cookie-name
(acceptor)
124 (declare (ignore acceptor
))
127 (defun start-server (&key
(http-port 8080) address
(common-root "/"))
128 "Start the presentation project server which listens on http-port
129 at address. Address defaults to all addresses of the local machine."
130 (setf *phoros-server
*
131 (make-instance 'hunchentoot
:acceptor
134 :access-logger
#'log-http-access
135 :message-logger
#'log-hunchentoot-message
))
136 (setf hunchentoot
:*session-max-time
* (* 3600 24))
137 (setf *common-root
* common-root
)
138 (check-db *postgresql-credentials
*)
139 (with-connection *postgresql-credentials
*
140 (assert-phoros-db-major-version))
141 (hunchentoot:reset-session-secret
)
142 (hunchentoot:start
*phoros-server
*))
144 (defun stop-server () (hunchentoot:stop
*phoros-server
*))
146 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
147 (register-sql-operators :2+-ary
:&& :overlaps
))
149 (setf hunchentoot
:*default-handler
*
151 "Http default response."
152 (setf (hunchentoot:return-code
*) hunchentoot
:+http-not-found
+)))
154 (hunchentoot:define-easy-handler phoros-handler
()
155 "First HTTP contact: if necessary, check credentials, establish new
157 (with-connection *postgresql-credentials
*
158 (let* ((presentation-project-name
159 (second (cl-utilities:split-sequence
160 #\
/ (hunchentoot:script-name
*) :remove-empty-subseqs t
)))
161 (presentation-project-id
164 (:select
'presentation-project-id
165 :from
'sys-presentation-project
166 :where
(:= 'presentation-project-name
167 presentation-project-name
))
170 ((null presentation-project-id
)
171 (setf (hunchentoot:return-code
*) hunchentoot
:+http-not-found
+))
172 ((and (equal (hunchentoot:session-value
'presentation-project-name
)
173 presentation-project-name
)
174 (hunchentoot:session-value
'authenticated-p
))
175 (hunchentoot:redirect
176 (format nil
"/phoros/lib/view-~A" (phoros-version))
180 (setf (hunchentoot:session-value
'presentation-project-name
)
181 presentation-project-name
)
182 (setf (hunchentoot:session-value
'presentation-project-id
)
183 presentation-project-id
)
184 (setf (hunchentoot:session-value
'presentation-project-bbox
)
187 (bounding-box (get-dao 'sys-presentation-project
188 presentation-project-name
)))))
189 (if (or (null bbox
) (eq :null bbox
))
192 (setf (hunchentoot:session-value
'aux-data-p
)
193 (with-connection *postgresql-aux-credentials
*
194 (view-exists-p (aux-point-view-name
195 presentation-project-name
))))
196 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
198 :style
"font-family:sans-serif;"
200 :method
"post" :enctype
"multipart/form-data"
201 :action
"/phoros/lib/authenticate" :name
"login-form"
203 (:legend
(:b
(:a
:href
"http://phoros.berlios.de"
204 :style
"text-decoration:none;"
206 (who:fmt
" [~A]" presentation-project-name
)))
208 (:b
(:em
"You can't do much without JavaScript there.")))
211 (:input
:type
"text" :name
"user-name"))
214 (:input
:type
"password" :name
"user-password")
216 (:span
:id
"cackle"))
217 (:input
:type
"submit" :value
"Submit"
219 (setf (chain document
220 (get-element-by-id "cackle")
222 "Ok, let's see…"))))
223 (:script
:type
"text/javascript"
224 (who:str
(ps (chain document
229 for i in
*login-intro
*
230 do
(who:htm
(:p
(who:str i
))))))))))))
232 (pushnew (hunchentoot:create-regex-dispatcher
"/phoros/(?!lib/)"
234 hunchentoot
:*dispatch-table
*)
236 (defun stored-bbox ()
237 "Return stored bounding box for user and presentation project of
239 (with-connection *postgresql-credentials
*
240 (let ((bbox (bounding-box
241 (get-dao 'sys-user-role
242 (hunchentoot:session-value
244 (hunchentoot:session-value
245 'presentation-project-id
)))))
247 (hunchentoot:session-value
'presentation-project-bbox
)
250 (defun stored-cursor ()
251 "Return stored cursor position for user and presentation project of
253 (with-connection *postgresql-credentials
*
256 (:select
(:st_x
'cursor
) (:st_y
'cursor
)
258 :where
(:and
(:= 'user-id
259 (hunchentoot:session-value
'user-id
))
260 (:= 'presentation-project-id
261 (hunchentoot:session-value
262 'presentation-project-id
))
263 (:raw
"cursor IS NOT NULL")))
266 (format nil
"~{~F~#^,~}" cursor
)))))
269 (hunchentoot:define-easy-handler
270 (authenticate-handler :uri
"/phoros/lib/authenticate"
271 :default-request-type
:post
)
273 "Check user credentials."
274 (with-connection *postgresql-credentials
*
275 (let* ((user-name (hunchentoot:post-parameter
"user-name"))
276 (user-password (hunchentoot:post-parameter
"user-password"))
277 (presentation-project-id (hunchentoot:session-value
278 'presentation-project-id
))
280 (when presentation-project-id
283 'sys-user.user-full-name
285 'sys-user-role.user-role
286 :from
'sys-user-role
'sys-user
288 (:= 'presentation-project-id presentation-project-id
)
289 (:= 'sys-user-role.user-id
'sys-user.user-id
)
290 (:= 'user-name user-name
)
291 (:= 'user-password user-password
)))
293 (user-full-name (first user-info
))
294 (user-id (second user-info
))
295 (user-role (third user-info
)))
298 (setf (hunchentoot:session-value
'authenticated-p
) t
299 (hunchentoot:session-value
'user-name
) user-name
300 (hunchentoot:session-value
'user-full-name
) user-full-name
301 (hunchentoot:session-value
'user-id
) user-id
302 (hunchentoot:session-value
'user-role
) user-role
)
303 (hunchentoot:redirect
(format nil
"/phoros/lib/view-~A"
306 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
308 :style
"font-family:sans-serif;"
310 (:a
:href
(format nil
"/phoros/~A/" (hunchentoot:session-value
311 'presentation-project-name
))
314 (hunchentoot:define-easy-handler logout-handler
(bbox longitude latitude
)
315 (if (hunchentoot:session-value
'authenticated-p
)
316 (with-connection *postgresql-credentials
*
317 (let ((presentation-project-name
318 (hunchentoot:session-value
'presentation-project-name
))
320 (get-dao 'sys-user-role
321 (hunchentoot:session-value
'user-id
)
322 (hunchentoot:session-value
'presentation-project-id
))))
325 (setf (bounding-box sys-user-role
) bbox
))
326 (when (and longitude latitude
)
327 (let* ;; gkludge: should be done by some library, not by DB query
328 ((point-form (format nil
"POINT(~F ~F)" longitude latitude
))
329 (point-wkb (query (:select
330 (:st_geomfromtext point-form
))
332 (setf (cursor sys-user-role
) point-wkb
)))
333 (update-dao sys-user-role
))
334 (hunchentoot:remove-session hunchentoot
:*session
*)
335 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
341 "Phoros: logged out" )))
342 (:link
:rel
"stylesheet"
343 :href
(format nil
"/phoros/lib/css-~A/style.css"
347 (:h1
:id
"title" "Phoros: logged out")
348 (:p
"Log back in to project "
349 (:a
:href
(format nil
"/phoros/~A" presentation-project-name
)
350 (who:fmt
"~A." presentation-project-name
))))))))
353 (pushnew (hunchentoot:create-regex-dispatcher
"/logout" 'logout-handler
)
354 hunchentoot
:*dispatch-table
*)
356 (define-condition superseded
() ()
358 "Tell a thread to finish as soon as possible taking any shortcuts
361 (hunchentoot:define-easy-handler
362 (local-data :uri
"/phoros/lib/local-data" :default-request-type
:post
)
364 "Receive coordinates, respond with the count nearest json objects
365 containing picture url, calibration parameters, and car position,
366 wrapped in an array. Wipe away any unfinished business first."
367 (when (hunchentoot:session-value
'authenticated-p
)
368 (dolist (old-thread (hunchentoot:session-value
'recent-threads
))
369 (ignore-errors (bt:interrupt-thread old-thread
#'(lambda () (signal 'superseded
)))))
370 (setf (hunchentoot:session-value
'recent-threads
) nil
)
371 (push (bt:current-thread
) (hunchentoot:session-value
'recent-threads
))
372 (setf (hunchentoot:content-type
*) "application/json")
373 (with-connection *postgresql-credentials
*
374 (let* ((presentation-project-id (hunchentoot:session-value
375 'presentation-project-id
))
376 (common-table-names (common-table-names
377 presentation-project-id
))
378 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
379 (longitude (cdr (assoc :longitude data
)))
380 (latitude (cdr (assoc :latitude data
)))
381 (count (cdr (assoc :count data
)))
382 (zoom (cdr (assoc :zoom data
)))
383 (snap-distance (* 1d-4
(expt 2 (- 22 zoom
)))) ; assuming geographic coordinates
384 (point-form (format nil
"POINT(~F ~F)" longitude latitude
))
389 ;; footprint is ready
396 for common-table-name in common-table-names
397 for aggregate-view-name
398 = (aggregate-view-name common-table-name
)
401 (:as
(:st_distance
'coordinates
404 ,*standard-coordinates
*))
407 'recorded-device-id
;debug
408 'device-stage-of-life-id
;debug
409 'generic-device-id
;debug
411 'filename
'byte-position
'point-id
412 (:as
(:not
(:is-null
'footprint
))
414 ,(when *render-footprints-p
*
415 '(:as
(:st_asewkt
'footprint
)
418 ;;'coordinates ;the search target
419 'longitude
'latitude
'ellipsoid-height
421 'east-sd
'north-sd
'height-sd
422 'roll
'pitch
'heading
423 'roll-sd
'pitch-sd
'heading-sd
424 'sensor-width-pix
'sensor-height-pix
426 'bayer-pattern
'color-raiser
428 'dx
'dy
'dz
'omega
'phi
'kappa
429 'c
'xh
'yh
'a1
'a2
'a3
'b1
'b2
'c1
'c2
'r0
430 'b-dx
'b-dy
'b-dz
'b-rotx
'b-roty
'b-rotz
432 'b-drotx
'b-droty
'b-drotz
434 ',aggregate-view-name
437 (:= 'presentation-project-id
438 ,presentation-project-id
)
448 for common-table-name
449 in common-table-names
450 for aggregate-view-name
451 = (aggregate-view-name
457 (:st_centroid
'footprint
)
460 ,*standard-coordinates
*))
462 (:as
(:st_centroid
'footprint
)
465 ',aggregate-view-name
468 (:= 'presentation-project-id
469 ,presentation-project-id
)
474 ,*standard-coordinates
*)
489 for common-table-name in common-table-names
490 for aggregate-view-name
491 = (aggregate-view-name common-table-name
)
494 (:as
(:st_distance
'coordinates
497 ,*standard-coordinates
*))
500 'recorded-device-id
;debug
501 'device-stage-of-life-id
;debug
502 'generic-device-id
;debug
504 'filename
'byte-position
'point-id
505 (:as
(:not
(:is-null
'footprint
))
508 ;;'coordinates ;the search target
509 'longitude
'latitude
'ellipsoid-height
511 'east-sd
'north-sd
'height-sd
512 'roll
'pitch
'heading
513 'roll-sd
'pitch-sd
'heading-sd
514 'sensor-width-pix
'sensor-height-pix
516 'bayer-pattern
'color-raiser
518 'dx
'dy
'dz
'omega
'phi
'kappa
519 'c
'xh
'yh
'a1
'a2
'a3
'b1
'b2
'c1
'c2
'r0
520 'b-dx
'b-dy
'b-dz
'b-rotx
'b-roty
'b-rotz
522 'b-drotx
'b-droty
'b-drotz
524 ',aggregate-view-name
526 (:and
(:= 'presentation-project-id
527 ,presentation-project-id
)
528 (:st_dwithin
'coordinates
531 ,*standard-coordinates
*)
536 (superseded () nil
))))
537 (when *render-footprints-p
*
541 for photo-parameter-set in result
542 for footprint-vertices
= ;something like this:
543 ;; "SRID=4326;POLYGON((14.334342229 51.723293508 118.492667334,14.334386877 51.723294417 118.404764286,14.334347429 51.72327914 118.506316418,14.334383211 51.723279895 118.435823396,14.334342229 51.723293508 118.492667334))"
544 (ignore-errors ;probably no :footprint-wkt
547 (parse-number:parse-real-number x
))
548 (cl-utilities:split-sequence
#\Space p
)))
550 (cl-utilities:split-sequence-if
555 (cdr (assoc :footprint-wkt photo-parameter-set
)))
558 (if footprint-vertices
559 (acons :rendered-footprint
561 '(:type
:coordinates
)
565 for footprint-vertex in footprint-vertices
566 for reprojected-vertex
=
569 ;; KLUDGE: translate keys, e.g. a1 -> a_1
570 (json:decode-json-from-string
571 (json:encode-json-to-string photo-parameter-set
))
572 (pairlis '(:x-global
:y-global
:z-global
)
575 (proj:degrees-to-radians
576 (first footprint-vertex
))
577 (proj:degrees-to-radians
578 (second footprint-vertex
))
579 (third footprint-vertex
))
581 (cdr (assoc :cartesian-system
582 photo-parameter-set
)))))
584 (list (cdr (assoc :m reprojected-vertex
))
585 (cdr (assoc :n reprojected-vertex
))))))
587 photo-parameter-set
))))
588 (json:encode-json-to-string result
)))))
590 (hunchentoot:define-easy-handler
591 (store-point :uri
"/phoros/lib/store-point" :default-request-type
:post
)
593 "Receive point sent by user; store it into database."
594 (when (hunchentoot:session-value
'authenticated-p
)
595 (let* ((presentation-project-name (hunchentoot:session-value
596 'presentation-project-name
))
597 (user-id (hunchentoot:session-value
'user-id
))
598 (user-role (hunchentoot:session-value
'user-role
))
599 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
600 (longitude (cdr (assoc :longitude data
)))
601 (latitude (cdr (assoc :latitude data
)))
602 (ellipsoid-height (cdr (assoc :ellipsoid-height data
)))
603 (stdx-global (cdr (assoc :stdx-global data
)))
604 (stdy-global (cdr (assoc :stdy-global data
)))
605 (stdz-global (cdr (assoc :stdz-global data
)))
606 (input-size (cdr (assoc :input-size data
)))
607 (attribute (cdr (assoc :attribute data
)))
608 (description (cdr (assoc :description data
)))
609 (numeric-description (cdr (assoc :numeric-description data
)))
611 (format nil
"SRID=4326; POINT(~S ~S ~S)"
612 longitude latitude ellipsoid-height
))
613 (aux-numeric-raw (cdr (assoc :aux-numeric data
)))
614 (aux-text-raw (cdr (assoc :aux-text data
)))
615 (aux-numeric (if aux-numeric-raw
616 (apply #'vector aux-numeric-raw
)
618 (aux-text (if aux-text-raw
619 (apply #'vector aux-text-raw
)
621 (user-point-table-name
622 (user-point-table-name presentation-project-name
)))
624 (not (string-equal user-role
"read")) ;that is, "write" or "admin"
625 () "No write permission.")
626 (with-connection *postgresql-credentials
*
628 (= 1 (execute (:insert-into user-point-table-name
:set
631 'description description
632 'numeric-description numeric-description
633 'creation-date
'current-timestamp
634 'coordinates
(:st_geomfromewkt point-form
)
635 'stdx-global stdx-global
636 'stdy-global stdy-global
637 'stdz-global stdz-global
638 'input-size input-size
639 'aux-numeric aux-numeric
640 'aux-text aux-text
)))
641 () "No point stored. This should not happen.")))))
643 (hunchentoot:define-easy-handler
644 (update-point :uri
"/phoros/lib/update-point" :default-request-type
:post
)
646 "Update point sent by user in database."
647 (when (hunchentoot:session-value
'authenticated-p
)
648 (let* ((presentation-project-name (hunchentoot:session-value
649 'presentation-project-name
))
650 (user-id (hunchentoot:session-value
'user-id
))
651 (user-role (hunchentoot:session-value
'user-role
))
652 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
653 (user-point-id (cdr (assoc :user-point-id data
)))
654 (attribute (cdr (assoc :attribute data
)))
655 (description (cdr (assoc :description data
)))
656 (numeric-description (cdr (assoc :numeric-description data
)))
657 (user-point-table-name
658 (user-point-table-name presentation-project-name
)))
660 (not (string-equal user-role
"read")) ;that is, "write" or "admin"
661 () "No write permission.")
662 (with-connection *postgresql-credentials
*
665 (:update user-point-table-name
:set
668 'description description
669 'numeric-description numeric-description
670 'creation-date
'current-timestamp
671 :where
(:and
(:= 'user-point-id user-point-id
)
672 (:or
(:= (if (string-equal user-role
683 () "No point stored. Did you try to update someone else's point ~
684 without having admin permission?")))))
686 (hunchentoot:define-easy-handler
687 (delete-point :uri
"/phoros/lib/delete-point" :default-request-type
:post
)
689 "Delete user point if user is allowed to do so."
690 (when (hunchentoot:session-value
'authenticated-p
)
691 (let* ((presentation-project-name (hunchentoot:session-value
692 'presentation-project-name
))
693 (user-id (hunchentoot:session-value
'user-id
))
694 (user-role (hunchentoot:session-value
'user-role
))
695 (user-point-table-name
696 (user-point-table-name presentation-project-name
))
697 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
))))
698 (with-connection *postgresql-credentials
*
700 (eql 1 (cond ((string-equal user-role
"admin")
701 (execute (:delete-from user-point-table-name
702 :where
(:= 'user-point-id data
))))
703 ((string-equal user-role
"write")
706 user-point-table-name
708 (:= 'user-point-id data
)
709 (:or
(:= 'user-id user-id
)
716 () "No point deleted. This should not happen.")))))
718 (defun common-table-names (presentation-project-id)
719 "Return a list of common-table-names of table sets that contain data
720 of presentation project with presentation-project-id."
723 (:select
'common-table-name
725 :from
'sys-presentation
'sys-measurement
'sys-acquisition-project
727 (:= 'sys-presentation.presentation-project-id
728 presentation-project-id
)
729 (:= 'sys-presentation.measurement-id
730 'sys-measurement.measurement-id
)
731 (:= 'sys-measurement.acquisition-project-id
732 'sys-acquisition-project.acquisition-project-id
)))
737 "While fetching common-table-names of presentation-project-id ~D: ~A"
738 presentation-project-id c
))))
740 (defun encode-geojson-to-string (features &key junk-keys
)
741 "Encode a list of property lists into a GeoJSON FeatureCollection.
742 Each property list must contain keys for coordinates, :x, :y, :z; it
743 may contain a numeric point :id and zero or more pieces of extra
744 information. The extra information is stored as GeoJSON Feature
745 properties. Exclude property list elements with keys that are in
747 (with-output-to-string (s)
748 (json:with-object
(s)
749 (json:encode-object-member
:type
:*feature-collection s
)
750 (json:as-object-member
(:features s
)
753 #'(lambda (point-with-properties)
754 (dolist (junk-key junk-keys
)
755 (remf point-with-properties junk-key
))
756 (destructuring-bind (&key x y z id
&allow-other-keys
) ;TODO: z probably bogus
757 point-with-properties
758 (json:as-array-member
(s)
759 (json:with-object
(s)
760 (json:encode-object-member
:type
:*feature s
)
761 (json:as-object-member
(:geometry s
)
762 (json:with-object
(s)
763 (json:encode-object-member
:type
:*point s
)
764 (json:as-object-member
(:coordinates s
)
765 (json:encode-json
(list x y z
) s
))))
766 (json:encode-object-member
:id id s
)
767 (json:as-object-member
(:properties s
)
768 (dolist (key '(:x
:y
:z
:id
))
769 (remf point-with-properties key
))
770 (json:encode-json-plist point-with-properties s
))))))
772 (json:encode-object-member
:phoros-version
(phoros-version) s
))))
775 "Return a WKT-compliant BOX3D string from string bbox."
776 (concatenate 'string
"BOX3D("
777 (substitute #\Space
#\
,
778 (substitute #\Space
#\
, bbox
:count
1)
779 :from-end t
:count
1)
782 (hunchentoot:define-easy-handler
(points :uri
"/phoros/lib/points.json") (bbox)
783 "Send a bunch of GeoJSON-encoded points from inside bbox to client."
784 (when (hunchentoot:session-value
'authenticated-p
)
785 (setf (hunchentoot:content-type
*) "application/json")
787 (with-connection *postgresql-credentials
*
788 (let* ((presentation-project-id
789 (hunchentoot:session-value
'presentation-project-id
))
791 (common-table-names presentation-project-id
)))
792 (encode-geojson-to-string
799 for common-table-name in common-table-names
800 for aggregate-view-name
801 = (point-data-table-name common-table-name
)
802 ;; would have been nice, was too slow:
803 ;; = (aggregate-view-name common-table-name)
806 (:as
(:st_x
'coordinates
) x
)
807 (:as
(:st_y
'coordinates
) y
)
808 (:as
(:st_z
'coordinates
) z
)
809 (:as
'point-id
'id
) ;becomes fid on client
812 :from
',aggregate-view-name
813 :natural
:left-join
'sys-presentation
816 (:= 'presentation-project-id
817 ,presentation-project-id
)
820 (:st_setsrid
(:type
,(box3d bbox
) box3d
)
821 ,*standard-coordinates
*))))))
823 ,*number-of-features-per-layer
*))
825 :junk-keys
'(:random
))))
828 :error
"While fetching points from inside bbox ~S: ~A"
831 (hunchentoot:define-easy-handler
(aux-points :uri
"/phoros/lib/aux-points.json") (bbox)
832 "Send a bunch of GeoJSON-encoded points from inside bbox to client."
833 (when (hunchentoot:session-value
'authenticated-p
)
834 (setf (hunchentoot:content-type
*) "application/json")
836 (let ((limit *number-of-features-per-layer
*)
838 (aux-point-view-name (hunchentoot:session-value
839 'presentation-project-name
))))
840 (encode-geojson-to-string
841 (with-connection *postgresql-aux-credentials
*
847 (:as
(:st_x
'coordinates
) 'x
)
848 (:as
(:st_y
'coordinates
) 'y
)
849 (:as
(:st_z
'coordinates
) 'z
)
853 (:st_setsrid
(:type
,(box3d bbox
) box3d
)
854 ,*standard-coordinates
*)))
860 :error
"While fetching aux-points from inside bbox ~S: ~A"
863 (hunchentoot:define-easy-handler
864 (aux-local-data :uri
"/phoros/lib/aux-local-data"
865 :default-request-type
:post
)
867 "Receive coordinates, respond with the count nearest json objects
868 containing arrays aux-numeric, aux-text, and distance to the
869 coordinates received, wrapped in an array."
870 (when (hunchentoot:session-value
'authenticated-p
)
871 (setf (hunchentoot:content-type
*) "application/json")
872 (let* ((aux-view-name
873 (aux-point-view-name (hunchentoot:session-value
874 'presentation-project-name
)))
875 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
876 (longitude (cdr (assoc :longitude data
)))
877 (latitude (cdr (assoc :latitude data
)))
878 (count (cdr (assoc :count data
)))
880 (format nil
"POINT(~F ~F)" longitude latitude
))
881 (snap-distance 1e-3) ;about 100 m, TODO: make this a defparameter
883 (format nil
"~A,~A,~A,~A"
884 (- longitude snap-distance
)
885 (- latitude snap-distance
)
886 (+ longitude snap-distance
)
887 (+ latitude snap-distance
))))
888 (encode-geojson-to-string
890 (with-connection *postgresql-aux-credentials
*
898 (:as
(:st_x
'coordinates
) 'x
)
899 (:as
(:st_y
'coordinates
) 'y
)
900 (:as
(:st_z
'coordinates
) 'z
)
907 ,*spherical-mercator
*)
909 (:st_geomfromtext
,point-form
,*standard-coordinates
*)
910 ,*spherical-mercator
*))
912 :from
',aux-view-name
913 :where
(:&& 'coordinates
915 ,(box3d bounding-box
) box3d
)
916 ,*standard-coordinates
*)))
921 (hunchentoot:define-easy-handler
922 (aux-local-linestring :uri
"/phoros/lib/aux-local-linestring.json"
923 :default-request-type
:post
)
925 "Receive longitude, latitude, radius, and step-size; respond
926 with the a JSON object comprising the elements linestring (a WKT
927 linestring stitched together of the nearest auxiliary points from
928 within radius around coordinates), current-point (the point on
929 linestring closest to coordinates), and previous-point and next-point
930 \(points on linestring step-size before and after current-point
932 (when (hunchentoot:session-value
'authenticated-p
)
933 (setf (hunchentoot:content-type
*) "application/json")
934 (let* ((thread-aux-points-function-name
935 (thread-aux-points-function-name (hunchentoot:session-value
936 'presentation-project-name
)))
937 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
938 (longitude (cdr (assoc :longitude data
)))
939 (latitude (cdr (assoc :latitude data
)))
940 (radius (cdr (assoc :radius data
)))
941 (step-size (cdr (assoc :step-size data
)))
942 (azimuth (if (numberp (cdr (assoc :azimuth data
)))
943 (cdr (assoc :azimuth data
))
946 (format nil
"POINT(~F ~F)" longitude latitude
))
949 (with-connection *postgresql-aux-credentials
*
955 (,thread-aux-points-function-name
957 ,point-form
,*standard-coordinates
*)
959 ,*number-of-points-per-aux-linestring
*
962 ,(proj:degrees-to-radians
91))))
964 (with-output-to-string (s)
965 (json:with-object
(s)
966 (json:encode-object-member
967 :linestring
(getf sql-response
:threaded-points
) s
)
968 (json:encode-object-member
969 :current-point
(getf sql-response
:current-point
) s
)
970 (json:encode-object-member
971 :previous-point
(getf sql-response
:back-point
) s
)
972 (json:encode-object-member
973 :next-point
(getf sql-response
:forward-point
) s
)
974 (json:encode-object-member
975 :azimuth
(getf sql-response
:new-azimuth
) s
))))))
977 (defun get-user-points (user-point-table-name &key
978 (bounding-box "-180,-90,180,90")
980 (order-criterion 'id
))
981 "Return limit points from user-point-table-name in GeoJSON format,
982 and the number of points returned."
983 (let ((user-point-plist
989 (:as
(:st_x
'coordinates
) 'x
)
990 (:as
(:st_y
'coordinates
) 'y
)
991 (:as
(:st_z
'coordinates
) 'z
)
992 (:as
'user-point-id
'id
) ;becomes fid in OpenLayers
993 'stdx-global
'stdy-global
'stdz-global
995 'attribute
'description
'numeric-description
997 (:as
(:to-char
'creation-date
998 ,*user-point-creation-date-format
*)
1000 'aux-numeric
'aux-text
1001 :from
,user-point-table-name
:natural
:left-join
'sys-user
1002 :where
(:&& 'coordinates
1003 (:st_setsrid
(:type
,(box3d bounding-box
) box3d
)
1004 ,*standard-coordinates
*)))
1009 (encode-geojson-to-string (nsubst nil
:null user-point-plist
))
1010 (length user-point-plist
))))
1012 (hunchentoot:define-easy-handler
1013 (user-points :uri
"/phoros/lib/user-points.json")
1015 "Send *number-of-features-per-layer* randomly chosen GeoJSON-encoded
1016 points from inside bbox to client. If there is no bbox parameter,
1018 (when (hunchentoot:session-value
'authenticated-p
)
1019 (setf (hunchentoot:content-type
*) "application/json")
1021 (let ((bounding-box (or bbox
"-180,-90,180,90"))
1022 (limit (if bbox
*number-of-features-per-layer
* :null
))
1023 (order-criterion (if bbox
'(:random
) 'id
))
1024 (user-point-table-name
1025 (user-point-table-name (hunchentoot:session-value
1026 'presentation-project-name
))))
1027 (with-connection *postgresql-credentials
*
1028 (nth-value 0 (get-user-points user-point-table-name
1029 :bounding-box bounding-box
1031 :order-criterion order-criterion
))))
1034 :error
"While fetching user-points~@[ from inside bbox ~S~]: ~A"
1037 (hunchentoot:define-easy-handler
1038 (user-point-attributes :uri
"/phoros/lib/user-point-attributes.json")
1040 "Send JSON object comprising arrays attributes and descriptions,
1041 each containing unique values called attribute and description
1042 respectively, and count being the frequency of value in the user point
1044 (when (hunchentoot:session-value
'authenticated-p
)
1045 (setf (hunchentoot:content-type
*) "application/json")
1047 (let ((user-point-table-name
1048 (user-point-table-name (hunchentoot:session-value
1049 'presentation-project-name
))))
1050 (with-connection *postgresql-credentials
*
1051 (with-output-to-string (s)
1052 (json:with-object
(s)
1053 (json:as-object-member
(:descriptions s
)
1054 (json:with-array
(s)
1055 (mapcar #'(lambda (x) (json:as-array-member
(s)
1056 (json:encode-json-plist x s
)))
1060 (:select
'description
1061 (:count
'description
)
1062 :from user-point-table-name
1063 :group-by
'description
)
1067 (json:as-object-member
(:attributes s
)
1068 (json:with-array
(s)
1069 (mapcar #'(lambda (x) (json:as-array-member
(s)
1070 (json:encode-json-plist x s
)))
1071 (query (format nil
"~
1072 (SELECT attribute, count(attribute) ~
1073 FROM ((SELECT attribute FROM ~A) ~
1076 FROM (VALUES ('solitary'), ~
1079 AS defaults(attribute))) ~
1080 AS attributes_union(attribute) ~
1081 GROUP BY attribute) ~
1082 ORDER BY attribute LIMIT 100"
1083 ;; Counts of solitary,
1084 ;; polyline, polygon may be
1085 ;; to big by one if we
1086 ;; collect them like this.
1087 (s-sql:to-sql-name user-point-table-name
))
1091 :error
"While fetching user-point-attributes: ~A"
1094 (hunchentoot:define-easy-handler photo-handler
1095 ((bayer-pattern :init-form
"65280,16711680")
1096 (color-raiser :init-form
"1,1,1")
1097 (mounting-angle :init-form
"0"))
1098 "Serve an image from a .pictures file."
1099 (when (hunchentoot:session-value
'authenticated-p
)
1102 (push (bt:current-thread
)
1103 (hunchentoot:session-value
'recent-threads
))
1104 (let* ((s (cdr (cl-utilities:split-sequence
1106 (hunchentoot:script-name
*)
1107 :remove-empty-subseqs t
)))
1108 (directory (last (butlast s
2)))
1109 (file-name-and-type (cl-utilities:split-sequence
1110 #\.
(first (last s
2))))
1111 (byte-position (parse-integer (car (last s
)) :junk-allowed t
))
1116 :directory
(append (pathname-directory *common-root
*)
1117 directory
'(:wild-inferiors
))
1118 :name
(first file-name-and-type
)
1119 :type
(second file-name-and-type
)))))
1121 (setf (hunchentoot:header-out
'cache-control
)
1122 (format nil
"max-age=~D" (* 3600 24 7)))
1123 (setf (hunchentoot:content-type
*) "image/png")
1124 (setf stream
(hunchentoot:send-headers
))
1126 stream path-to-file byte-position
1128 (apply #'vector
(mapcar
1130 (cl-utilities:split-sequence
1131 #\
, bayer-pattern
)))
1133 (apply #'vector
(mapcar
1134 #'parse-number
:parse-positive-real-number
1135 (cl-utilities:split-sequence
#\
, color-raiser
)))
1136 :reversep
(= 180 (parse-integer mounting-angle
)))))
1140 :error
"While serving image ~S: ~A" (hunchentoot:request-uri
*) c
)))))
1142 (pushnew (hunchentoot:create-prefix-dispatcher
"/phoros/lib/photo"
1144 hunchentoot
:*dispatch-table
*)
1146 ;;; for debugging; this is the multi-file OpenLayers
1147 (pushnew (hunchentoot:create-folder-dispatcher-and-handler
1148 "/phoros/lib/openlayers/" "OpenLayers-2.10/")
1149 hunchentoot
:*dispatch-table
*)
1151 (pushnew (hunchentoot:create-folder-dispatcher-and-handler
"/phoros/lib/ol/" "ol/")
1152 hunchentoot
:*dispatch-table
*)
1154 (pushnew (hunchentoot:create-folder-dispatcher-and-handler
1155 (format nil
"/phoros/lib/css-~A/" (phoros-version)) "css/") ;TODO: merge this style.css into public_html/style.css
1156 hunchentoot
:*dispatch-table
*)
1158 (pushnew (hunchentoot:create-folder-dispatcher-and-handler
1159 "/phoros/lib/public_html/" "public_html/")
1160 hunchentoot
:*dispatch-table
*)
1162 (pushnew (hunchentoot:create-static-file-dispatcher-and-handler
1163 "/favicon.ico" "public_html/favicon.ico")
1164 hunchentoot
:*dispatch-table
*)
1166 (hunchentoot:define-easy-handler
1167 (view :uri
(format nil
"/phoros/lib/view-~A" (phoros-version))
1168 :default-request-type
:post
)
1170 "Serve the client their main workspace."
1172 (hunchentoot:session-value
'authenticated-p
)
1173 (who:with-html-output-to-string
(s nil
:prologue t
:indent t
)
1179 "Phoros: " (hunchentoot:session-value
1180 'presentation-project-name
))))
1181 (if *use-multi-file-openlayers
*
1183 (:script
:src
"/phoros/lib/openlayers/lib/Firebug/firebug.js")
1184 (:script
:src
"/phoros/lib/openlayers/lib/OpenLayers.js"))
1185 (who:htm
(:script
:src
"/phoros/lib/ol/OpenLayers.js")))
1186 (:link
:rel
"stylesheet"
1187 :href
(format nil
"/phoros/lib/css-~A/style.css"
1190 (:script
:src
(format ;variability in script name is
1191 nil
; supposed to fight browser cache
1192 "/phoros/lib/phoros-~A-~A-~A.js"
1194 (hunchentoot:session-value
'user-name
)
1195 (hunchentoot:session-value
'presentation-project-name
)))
1196 (:script
:src
"http://maps.google.com/maps/api/js?sensor=false"))
1199 (:noscript
(:b
(:em
"You can't do much without JavaScript here.")))
1201 "Phoros: " (who:str
(hunchentoot:session-value
'user-full-name
))
1202 (who:fmt
" (~A)" (hunchentoot:session-value
'user-name
))
1203 "with " (:span
:id
"user-role"
1204 (who:str
(hunchentoot:session-value
'user-role
)))
1206 (:span
:id
"presentation-project-name"
1207 (who:str
(hunchentoot:session-value
1208 'presentation-project-name
)))
1209 (:span
:id
"presentation-project-emptiness")
1210 (:span
:id
"phoros-version" :class
"h1-right"
1211 (who:fmt
"v~A" (phoros-version))))
1212 (:div
:class
"controlled-streetmap"
1213 (:div
:id
"streetmap" :class
"streetmap" :style
"cursor:crosshair")
1214 (:div
:id
"streetmap-controls" :class
"streetmap-controls"
1215 (:div
:id
"streetmap-vertical-strut"
1216 :class
"streetmap-vertical-strut")
1217 (:div
:id
"streetmap-layer-switcher"
1218 :class
"streetmap-layer-switcher")
1219 (:div
:id
"streetmap-overview" :class
"streetmap-overview")
1220 (:div
:id
"streetmap-mouse-position"
1221 :class
"streetmap-mouse-position")
1222 (:div
:id
"streetmap-zoom" :class
"streetmap-zoom")))
1223 (:div
:class
"phoros-controls"
1224 (:div
:id
"real-phoros-controls"
1225 (:h2
(:span
:id
"h2-controls") (:span
:id
"creator"))
1226 (:div
:id
"point-attribute"
1228 (:select
:id
"point-attribute-select"
1229 :name
"point-attribute-select"
1230 :class
"combobox-select"
1233 (consolidate-combobox "point-attribute"))
1235 (:input
:id
"point-attribute-input"
1236 :name
"point-attribute-input"
1237 :class
"combobox-input"
1238 :onchange
(ps-inline
1239 (unselect-combobox-selection
1243 ;; (:select :id "point-attribute" :disabled t
1244 ;; :size 1 :name "point-attribute")
1245 (:input
:id
"point-numeric-description"
1246 :class
"vanilla-input"
1248 :type
"text" :name
"point-numeric-description")
1250 (:div
:id
"point-description"
1252 (:select
:id
"point-description-select"
1253 :name
"point-description-select"
1254 :class
"combobox-select"
1255 :onchange
(ps-inline
1256 (consolidate-combobox
1257 "point-description"))
1259 (:input
:id
"point-description-input"
1260 :name
"point-description-input"
1261 :class
"combobox-input"
1262 :onchange
(ps-inline
1263 (unselect-combobox-selection
1264 "point-description"))
1267 (:button
:id
"delete-point-button" :disabled t
1269 :onclick
(ps-inline (delete-point))
1271 (:button
:disabled t
:id
"finish-point-button"
1274 (:div
:id
"aux-point-distance-or-point-creation-date"
1275 (:code
:id
"point-creation-date")
1276 (:select
:id
"aux-point-distance" :disabled t
1277 :size
1 :name
"aux-point-distance"
1278 :onchange
(ps-inline
1279 (aux-point-distance-selected))
1281 (enable-aux-point-selection)))
1282 (:div
:id
"include-aux-data"
1284 (:input
:id
"include-aux-data-p"
1285 :class
"tight-input"
1286 :type
"checkbox" :checked t
1287 :name
"include-aux-data-p"
1288 :onchange
(ps-inline
1289 (flip-aux-data-inclusion)))
1291 (:div
:id
"aux-data"
1292 (:div
:id
"aux-numeric-list")
1293 (:div
:id
"aux-text-list")))
1294 (:div
:id
"multiple-points-phoros-controls"
1295 (:h2
"Multiple Points Selected")
1296 (:p
"You have selected multiple user points.")
1297 (:p
"Unselect all but one to edit or view its properties."))
1298 (:div
:class
"walk-mode-controls"
1299 (:div
:id
"walk-mode"
1301 (:input
:id
"walk-p"
1302 :class
"tight-input"
1303 :type
"checkbox" :checked nil
1304 :onchange
(ps-inline
1307 (:div
:id
"decrease-step-size"
1308 :onclick
(ps-inline (decrease-step-size)))
1309 (:div
:id
"step-size"
1310 :onclick
(ps-inline (increase-step-size))
1312 (:div
:id
"increase-step-size"
1313 :onclick
(ps-inline (increase-step-size))
1314 :ondblclick
(ps-inline (increase-step-size)
1315 (increase-step-size)))
1316 (:div
:id
"step-button" :disabled nil
1317 :onclick
(ps-inline (step))
1318 :ondblclick
(ps-inline (step t
))
1320 (:div
:class
"image-main-controls"
1321 (:div
:id
"auto-zoom"
1323 (:input
:id
"zoom-to-point-p"
1324 :class
"tight-input"
1325 :type
"checkbox" :checked t
)
1327 (:div
:id
"zoom-images-to-max-extent"
1328 :onclick
(ps-inline (zoom-images-to-max-extent)))
1329 (:div
:id
"no-footprints-p"
1331 (:div
:id
"remove-work-layers-button" :disabled t
1332 :onclick
(ps-inline (reset-layers-and-controls))
1334 (:div
:class
"help-div"
1335 (:button
:id
"download-user-points-button"
1337 :onclick
"self.location.href = \"/phoros/lib/user-points.json\""
1338 "download points") ;TODO: offer other formats and maybe projections
1339 (:button
:id
"blurb-button"
1344 (+ "/phoros/lib/blurb?openlayers-version="
1345 (@ *open-layers
*version_number
*))
1347 (:img
:src
"/phoros/lib/public_html/phoros-logo-plain.png"
1348 :alt
"Phoros" :style
"vertical-align:middle"
1350 (:button
:id
"logout-button"
1352 :onclick
(ps-inline (bye))
1354 (:h2
:id
"h2-help" "Help")
1355 (:div
:id
"help-display"))
1356 (:div
:id
"images" :style
"clear:both"
1358 for i from
0 below
*number-of-images
* do
1360 (:div
:class
"controlled-image"
1361 (:div
:id
(format nil
"image-~S-controls" i
)
1362 :class
"image-controls"
1363 (:div
:id
(format nil
"image-~S-zoom" i
)
1364 :class
"image-zoom")
1365 (:div
:id
(format nil
"image-~S-layer-switcher" i
)
1366 :class
"image-layer-switcher")
1367 (:div
:id
(format nil
"image-~S-usable" i
)
1368 :class
"image-usable"
1370 (:div
:id
(format nil
"image-~S-trigger-time" i
)
1371 :class
"image-trigger-time"))
1372 (:div
:id
(format nil
"image-~S" i
)
1373 :class
"image" :style
"cursor:crosshair"))))))))
1374 (hunchentoot:redirect
1375 (concatenate 'string
"/phoros/" (hunchentoot:session-value
1376 'presentation-project-name
))
1377 :add-session-id t
)))
1379 (hunchentoot:define-easy-handler
1380 (epipolar-line :uri
"/phoros/lib/epipolar-line")
1382 "Receive vector of two sets of picture parameters, respond with
1383 JSON encoded epipolar-lines."
1384 (when (hunchentoot:session-value
'authenticated-p
)
1385 (setf (hunchentoot:content-type
*) "application/json")
1386 (let* ((data (json:decode-json-from-string
(hunchentoot:raw-post-data
))))
1387 (json:encode-json-to-string
1388 (photogrammetry :epipolar-line
(first data
) (second data
))))))
1390 (hunchentoot:define-easy-handler
1391 (estimated-positions :uri
"/phoros/lib/estimated-positions")
1393 "Receive a two-part JSON vector comprising (1) a vector containing
1394 sets of picture-parameters with clicked (\"active\") points
1395 stored in :m, :n; and (2) a vector containing sets of
1396 picture-parameters; respond with a JSON encoded two-part vector
1397 comprising (1) a point in global coordinates; and (2) a vector of
1398 image coordinates (m, n) for the global point that correspond to the
1399 images from the received second vector. TODO: report error on bad
1400 data (ex: points too far apart)."
1401 ;; TODO: global-point-for-display should probably contain a proj string in order to make sense of the (cartesian) standard deviations.
1402 (when (hunchentoot:session-value
'authenticated-p
)
1403 (setf (hunchentoot:content-type
*) "application/json")
1405 (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
1406 (active-point-photo-parameters
1408 (number-of-active-points
1409 (length active-point-photo-parameters
))
1410 (destination-photo-parameters
1413 (cdr (assoc :cartesian-system
1414 (first active-point-photo-parameters
))))
1415 (global-point-cartesian
1417 :multi-position-intersection active-point-photo-parameters
))
1418 (global-point-geographic-radians
1419 (proj:cs2cs
(list (cdr (assoc :x-global global-point-cartesian
))
1420 (cdr (assoc :y-global global-point-cartesian
))
1421 (cdr (assoc :z-global global-point-cartesian
)))
1422 :source-cs cartesian-system
))
1423 (global-point-for-display ;points geographic cs, degrees; std deviations in cartesian cs
1424 (pairlis '(:longitude
:latitude
:ellipsoid-height
1425 :stdx-global
:stdy-global
:stdz-global
1428 (proj:radians-to-degrees
1429 (first global-point-geographic-radians
))
1430 (proj:radians-to-degrees
1431 (second global-point-geographic-radians
))
1432 (third global-point-geographic-radians
)
1433 (cdr (assoc :stdx-global global-point-cartesian
))
1434 (cdr (assoc :stdy-global global-point-cartesian
))
1435 (cdr (assoc :stdz-global global-point-cartesian
))
1436 number-of-active-points
)))
1439 for i in destination-photo-parameters
1442 (photogrammetry :reprojection i global-point-cartesian
)))))
1443 (json:encode-json-to-string
1444 (list global-point-for-display image-coordinates
)))))
1446 (hunchentoot:define-easy-handler
1447 (user-point-positions :uri
"/phoros/lib/user-point-positions")
1449 "Receive a two-part JSON vector comprising
1450 - a vector of user-point-id's and
1451 - a vector containing sets of picture-parameters;
1452 respond with a JSON object comprising the elements
1453 - image-points, a vector whose elements
1454 - correspond to the elements of the picture-parameters vector
1456 - are GeoJSON feature collections containing one point (in picture
1457 coordinates) for each user-point-id received;
1458 - user-point-count, the number of user-points we tried to fetch
1460 (when (hunchentoot:session-value
'authenticated-p
)
1461 (setf (hunchentoot:content-type
*) "application/json")
1462 (let* ((user-point-table-name
1463 (user-point-table-name (hunchentoot:session-value
1464 'presentation-project-name
)))
1465 (data (json:decode-json-from-string
(hunchentoot:raw-post-data
)))
1466 (user-point-ids (first data
))
1467 (user-point-count (length user-point-ids
))
1468 (destination-photo-parameters (second data
))
1470 (cdr (assoc :cartesian-system
1471 (first destination-photo-parameters
)))) ;TODO: in rare cases, coordinate systems of the images shown may differ
1473 (with-connection *postgresql-credentials
*
1476 (:as
(:st_x
'coordinates
) 'longitude
)
1477 (:as
(:st_y
'coordinates
) 'latitude
)
1478 (:as
(:st_z
'coordinates
) 'ellipsoid-height
)
1479 (:as
'user-point-id
'id
) ;becomes fid on client
1482 'numeric-description
1484 (:as
(:to-char
'creation-date
"IYYY-MM-DD HH24:MI:SS TZ")
1488 :from user-point-table-name
:natural
:left-join
'sys-user
1489 :where
(:in
'user-point-id
(:set user-point-ids
)))
1491 (global-points-cartesian
1493 for global-point-geographic in user-points
1495 (ignore-errors ;in case no destination-photo-parameters have been sent
1496 (pairlis '(:x-global
:y-global
:z-global
)
1499 (proj:degrees-to-radians
1500 (getf global-point-geographic
:longitude
))
1501 (proj:degrees-to-radians
1502 (getf global-point-geographic
:latitude
))
1503 (getf global-point-geographic
:ellipsoid-height
))
1504 :destination-cs cartesian-system
)))))
1507 for photo-parameter-set in destination-photo-parameters
1509 (encode-geojson-to-string
1511 for global-point-cartesian in global-points-cartesian
1512 for user-point in user-points
1515 (let ((photo-coordinates
1516 (photogrammetry :reprojection
1518 global-point-cartesian
))
1521 (setf (getf photo-point
:x
)
1522 (cdr (assoc :m photo-coordinates
)))
1523 (setf (getf photo-point
:y
)
1524 (cdr (assoc :n photo-coordinates
)))
1526 :junk-keys
'(:longitude
:latitude
:ellipsoid-height
)))))
1527 (with-output-to-string (s)
1528 (json:with-object
(s)
1529 (json:encode-object-member
:user-point-count user-point-count s
)
1530 (json:as-object-member
(:image-points s
)
1531 (json:with-array
(s)
1532 (loop for i in image-coordinates do
1533 (json:as-array-member
(s) (princ i s
))))))))))
1535 (hunchentoot:define-easy-handler
1536 (multi-position-intersection :uri
"/phoros/lib/intersection")
1538 "Receive vector of sets of picture parameters, respond with stuff."
1539 (when (hunchentoot:session-value
'authenticated-p
)
1540 (setf (hunchentoot:content-type
*) "application/json")
1541 (let* ((data (json:decode-json-from-string
(hunchentoot:raw-post-data
))))
1542 (json:encode-json-to-string
1543 (photogrammetry :multi-position-intersection data
)))))