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.
19 ;;;; The UNIX command line interface
21 ;; TODO: options that have a function as their :action seem to mask earlier options. Fix or document.
25 (defparameter *cli-general-options
*
26 '((("help" #\h
) :action
#'cli-help-action
27 :documentation
"Print this help and exit.")
28 (("licence" "license") :action
#'cli-licence-action
29 :documentation
"Print licence boilerplate and exit.")
30 ("version" :action
#'cli-version-action
31 :documentation
"Print version information and exit. Use --verbose=1 to see more. In a version string A.B.C, changes in A denote incompatible changes in data; changes in B mean user-visible changes in feature set.")
32 ("verbose" :type integer
:initial-value
0
33 :documentation
"Dependent on bits set in this integer, emit various kinds of debugging output. ")
34 ("log-dir" :type string
:initial-value
""
35 :documentation
"Where to put the log files. Created if necessary; should end with a slash.")
36 ("check-db" :action
#'check-db-action
37 :documentation
"Check connection to databases (including auxiliary if applicable) and exit.")
38 ("check-dependencies" :action
#'check-dependencies-action
39 :documentation
"Check presence of dependencies on local system and exit.")
40 ("nuke-all-tables" :action
#'nuke-all-tables-action
41 :documentation
"Ask for confirmation, then delete anything in database and exit.")
42 ("create-sys-tables" :action
#'create-sys-tables-action
43 :documentation
"Ask for confirmation, then create in database a set of sys-* tables (tables shared between all projects). The database should probably be empty before you try this.")))
45 (defparameter *cli-db-connection-options
*
46 '((("host" #\H
) :type string
:initial-value
"localhost"
47 :documentation
"Database server.")
48 (("port" #\P
) :type integer
:initial-value
5432
49 :documentation
"Port on database server.")
50 (("database" #\D
) :type string
:initial-value
"phoros"
51 :documentation
"Name of database.")
52 (("user" #\U
) :type string
53 :documentation
"Database user.")
54 (("password" #\W
) :type string
55 :documentation
"Database user's password.")
56 ("use-ssl" :type string
:initial-value
"no"
57 :documentation
"Use SSL in database connection. [yes|no|try]")))
59 (defparameter *cli-aux-db-connection-options
*
60 '(("aux-host" :type string
61 :documentation
"Auxiliary database server. (default: same as --host)")
62 ("aux-port" :type integer
63 :documentation
"Port on auxiliary database server. (default: same as --port)")
64 ("aux-database" :type string
65 :documentation
"Name of auxiliary database. (defaul: same as --database)")
66 ("aux-user" :type string
67 :documentation
"Auxiliary database user. (default: same as --user)")
68 ("aux-password" :type string
69 :documentation
"Auxiliary database user's password. (default: same as --password)")
70 ("aux-use-ssl" :type string
71 :documentation
"Use SSL in auxiliary database connection. [yes|no|try] (default: same as --use-ssl)")))
73 (defparameter *cli-get-image-options
*
74 '(("get-image" :action
#'get-image-action
75 :documentation
"Get a single image from a .pictures file, print its trigger-time to stdout, and exit.")
76 ("count" :type integer
:initial-value
0
77 :documentation
"Image number in .pictures file.")
78 ("byte-position" :type integer
79 :documentation
"Byte position of image in .pictures file.")
81 :documentation
"Path to .pictures file.")
82 ("out" :type string
:initial-value
"phoros-get-image.png"
83 :documentation
"Path to to output .png file.")
84 ;; The way it should be had we two-dimensional arrays in postmodern:
85 ;;("bayer-pattern" :type string :list t :optional t :action :raw-bayer-pattern :documentation "The first pixels of the first row. Repeat this option to describe following row(s). Each pixel is to be interpreted as RGB hex string. Example: use #ff0000,#00ff00 if the first pixels in topmost row are red, green.")
86 ("bayer-pattern" :type string
:initial-value
"#ff0000,#00ff00" :action
:raw-bayer-pattern
87 :documentation
"The first pixels of the first row. Each pixel is to be interpreted as RGB hex string. Example: use #ff0000,#00ff00 if the first pixels in topmost row are red, green.")))
89 (defparameter *cli-camera-hardware-options
*
90 '(("store-camera-hardware" :action
#'store-camera-hardware-action
91 :documentation
"Put new camera-hardware data into the database; print camera-hardware-id to stdout.")
92 ("sensor-width-pix" :type integer
93 :documentation
"Width of camera sensor.")
94 ("sensor-height-pix" :type integer
95 :documentation
"Height of camera sensor.")
96 ("pix-size" :type string
97 :documentation
"Camera pixel size in millimetres (float).")
98 ("channels" :type integer
99 :documentation
"Number of color channels")
100 ("pix-depth" :type integer
:initial-value
255
101 :documentation
"Greatest possible pixel value.")
102 ("color-raiser" :type string
:initial-value
"1,1,1"
103 :action
:raw-color-raiser
104 :documentation
"Multipliers for the individual color components. Example: 1.2,1,.8 multiplies red by 1.2 and blue by 0.8.")
105 ;; The way it should be had we two-dimensional arrays in postmodern:
106 ;;("bayer-pattern" :type string :list t :optional t :action :raw-bayer-pattern :documentation "The first pixels of the first row. Repeat this option to describe following row(s). Each pixel is to be interpreted as RGB hex string. Example: use #ff0000,#00ff00 if the first pixels in topmost row are red, green.")
107 ("bayer-pattern" :type string
:optional t
108 :action
:raw-bayer-pattern
109 :documentation
"The first pixels of the first row. Each pixel is to be interpreted as RGB hex string. Example: use #ff0000,#00ff00 if the first pixels in topmost row are red, green.")
110 ("serial-number" :type string
111 :documentation
"Serial number.")
112 ("description" :type string
113 :documentation
"Description of camera.")
114 ("try-overwrite" :type boolean
:initial-value
"yes"
115 :documentation
"Overwrite matching camera-hardware record if any.")))
117 (defparameter *cli-lens-options
*
118 '(("store-lens" :action
#'store-lens-action
119 :documentation
"Put new lens data into the database; print lens-id to stdout.")
121 :documentation
"Nominal focal length in millimetres.")
122 ("serial-number" :type string
123 :documentation
"Serial number.")
124 ("description" :type string
125 :documentation
"Lens desription.")
126 ("try-overwrite" :type boolean
:initial-value
"yes"
127 :documentation
"Overwrite matching lens record if any.")))
129 (defparameter *cli-generic-device-options
*
130 '(("store-generic-device" :action
#'store-generic-device-action
131 :documentation
"Put a newly defined generic-device into the database; print generic-device-id to stdout.")
132 ("camera-hardware-id" :type integer
133 :documentation
"Numeric camera hardware id in database.")
134 ("lens-id" :type integer
135 :documentation
"Numeric lens id in database.")))
137 (defparameter *cli-device-stage-of-life-options
*
138 '(("store-device-stage-of-life" :action
#'store-device-stage-of-life-action
139 :documentation
"Put a newly defined device-stage-of-life into the database; print device-stage-of-life-id to stdout.")
140 ("recorded-device-id" :type string
141 :documentation
"Device id stored next to the measuring data.")
142 ("event-number" :type string
143 :documentation
"GPS event that triggers this generic device.")
144 ("generic-device-id" :type integer
145 :documentation
"Numeric generic-device id in database.")
146 ("vehicle-name" :type string
147 :documentation
"Descriptive name of vehicle.")
148 ("casing-name" :type string
149 :documentation
"Descriptive name of device casing.")
150 ("computer-name" :type string
151 :documentation
"Name of the recording device.")
152 ("computer-interface-name" :type string
153 :documentation
"Interface at device.")
154 ("mounting-date" :type string
155 :documentation
"Time this device constellation became effective. Format: \"2010-11-19T13:49+01\".")))
157 (defparameter *cli-device-stage-of-life-end-options
*
158 '(("store-device-stage-of-life-end" :action
#'store-device-stage-of-life-end-action
159 :documentation
"Put an end date to a device-stage-of-life in the database; print device-stage-of-life-id to stdout.")
160 ("device-stage-of-life-id" :type string
161 :documentation
"Id of the device-stage-of-life to put to an end.")
162 ("unmounting-date" :type string
163 :documentation
"Time this device constellation ceased to be effective. Format: \"2010-11-19T17:02+01\".")))
165 (defparameter *cli-camera-calibration-options
*
166 '(("store-camera-calibration" :action
#'store-camera-calibration-action
167 :documentation
"Put new camera-calibration into the database; print generic-device-id and calibration date to stdout.")
168 ("device-stage-of-life-id" :type string
169 :documentation
"This tells us what hardware this calibration is for.")
171 :documentation
"Date of calibration. Format: \"2010-11-19T13:49+01\".")
172 ("person" :type string
173 :documentation
"Person who did the calibration.")
174 ("main-description" :type string
175 :documentation
"Regarding this entire set of calibration data")
176 ("debug" :type string
177 :documentation
"If true: not for production use; may be altered or deleted at any time.")
178 ("photogrammetry-version" :type string
179 :documentation
"Software version used to create this data.")
180 ("mounting-angle" :type integer
181 :documentation
"Head up = 0; right ear up = 90; left ear up = -90; head down = 180.")
182 ("inner-orientation-description" :type string
183 :documentation
"Comments regarding inner orientation calibration.")
184 ("c" :type string
:documentation
"Inner orientation: focal length.")
186 :documentation
"Inner orientation: principal point displacement.")
188 :documentation
"Inner orientation: principal point displacement.")
189 ("a1" :type string
:documentation
"Inner orientation: radial distortion.")
190 ("a2" :type string
:documentation
"Inner orientation: radial distortion.")
191 ("a3" :type string
:documentation
"Inner orientation: radial distortion.")
193 :documentation
"Inner orientation: asymmetric and tangential distortion.")
195 :documentation
"Inner orientation: asymmetric and tangential distortion.")
197 :documentation
"Inner orientation: affinity and shear distortion.")
199 :documentation
"Inner orientation: affinity and shear distortion.")
200 ("r0" :type string
:documentation
"Inner orientation.")
201 ("outer-orientation-description" :type string
202 :documentation
"Comments regarding outer orientation calibration.")
203 ("dx" :type string
:documentation
"Outer orientation; in metres.")
204 ("dy" :type string
:documentation
"Outer orientation; in metres.")
205 ("dz" :type string
:documentation
"Outer orientation; in metres.")
206 ("omega" :type string
:documentation
"Outer orientation.")
207 ("phi" :type string
:documentation
"Outer orientation.")
208 ("kappa" :type string
:documentation
"Outer orientation.")
209 ("boresight-description" :type string
210 :documentation
"Comments regarding boresight alignment calibration.")
211 ("b-dx" :type string
:documentation
"Boresight alignment.")
212 ("b-dy" :type string
:documentation
"Boresight alignment.")
213 ("b-dz" :type string
:documentation
"Boresight alignment.")
214 ("b-ddx" :type string
:documentation
"Boresight alignment.")
215 ("b-ddy" :type string
:Documentation
"Boresight alignment.")
216 ("b-ddz" :type string
:documentation
"Boresight alignment.")
217 ("b-rotx" :type string
:documentation
"Boresight alignment.")
218 ("b-roty" :type string
:documentation
"Boresight alignment.")
219 ("b-rotz" :type string
:documentation
"Boresight alignment.")
220 ("b-drotx" :type string
:documentation
"Boresight alignment.")
221 ("b-droty" :type string
:documentation
"Boresight alignment.")
222 ("b-drotz" :type string
:documentation
"Boresight alignment.")
224 :documentation
"X component of unit vector of vehicle ground plane.")
226 :documentation
"Y component of unit vector of vehicle ground plane.")
228 :documentation
"Z component of unit vector of vehicle ground plane.")
229 ("d" :type string
:documentation
"Distance of vehicle ground plane.")))
231 (defparameter *cli-acquisition-project-options
*
232 '(("create-acquisition-project"
233 :type string
:action
#'create-acquisition-project-action
234 :documentation
"Create a fresh set of canonically named data tables. The string argument is the acquisition project name. It will be stored in table sys-acquisition-project, field common-table-name, and used as a common part of the data table names.")
235 ("delete-acquisition-project"
236 :type string
:action
#'delete-acquisition-project-action
237 :documentation
"Ask for confirmation, then delete acquisition project and all its measurements.")
238 ("delete-measurement"
239 :type integer
:action
#'delete-measurement-action
240 :documentation
"Delete a measurement by its ID.")
241 ("list-acquisition-project"
242 :type string
:optional t
:action
#'list-acquisition-project-action
243 :documentation
"List measurements of one acquisition project if its name is specified, or of all acquisition projects otherwise.")))
245 (defparameter *cli-store-images-and-points-options
*
246 '((("store-images-and-points" #\s
) :type string
:action
#'store-images-and-points-action
247 :documentation
"Link images to GPS points; store both into their respective DB tables. Images become linked to GPS points when their respective times differ by less than epsilon seconds, and when the respective events match. The string argument is the acquisition project name.")
248 (("directory" #\d
) :type string
249 :documentation
"Directory containing one set of measuring data.")
250 (("common-root" #\r) :type string
251 :documentation
"The root part of directory that is equal for all pojects. TODO: come up with some sensible default.")
252 ("epsilon" :type string
:initial-value
".001"
253 :documentation
"Difference in seconds below which two timestamps are considered equal.")
254 ("aggregate-events" :type nil
255 :documentation
"Put all GPS points in one bucket, disregarding any event numbers. Use this if you have morons setting up your generic-device. Hundreds of orphaned images may indicate this is the case.")))
257 (defparameter *cli-start-server-options
*
258 '(("server" :action
#'server-action
259 :documentation
"Start HTTP presentation server. Entry URI is http://<host>:<port>/phoros/<presentation-project>")
260 ("address" :type string
261 :documentation
"Address (of local machine) server is to listen to. Default is listening to all available addresses.")
262 ("http-port" :type integer
:initial-value
8080
263 :documentation
"Port the presentation server listens on.")
264 (("common-root" #\r) :type string
:initial-value
"/"
265 :documentation
"The root part of directory that is equal for all pojects. TODO: come up with some sensible default.")
266 ("images" :type integer
:initial-value
4 :action
*number-of-images
*
267 :documentation
"Number of photos shown to the HTTP client.")))
269 (defparameter *cli-presentation-project-options
*
270 '(("create-presentation-project"
271 :type string
:action
#'create-presentation-project-action
272 :documentation
"Create a fresh presentation project which is to expose a set of measurements to certain users.")
273 ("delete-presentation-project"
274 :type string
:action
#'delete-presentation-project-action
275 :documentation
"Ask for confirmation, then delete the presentation project including its table of user-generated points.")
276 ("list-presentation-project"
277 :type string
:optional t
:action
#'list-presentation-project-action
278 :documentation
"List one presentation project if specified, or all presentation projects if not.")
279 ("add-to-presentation-project"
280 :type string
:action
#'add-to-presentation-project-action
281 :documentation
"Add to the presentation project given either certain measurements or all measurements currently in a certain acquisition project.")
282 ("remove-from-presentation-project"
283 :type string
:action
#'remove-from-presentation-project-action
284 :documentation
"Remove from the presentation project given either certain measurements or all measurements currently in a certain acquisition project.")
285 ("measurement-id" :type integer
:list t
:optional t
286 :documentation
"One measurement-id to add or remove. Repeat if necessary.")
287 ("acquisition-project" :type string
288 :documentation
"The acquisition project whose measurements are to add or remove.")))
290 (defparameter *cli-aux-view-options
*
292 :type string
:action
#'create-aux-view-action
293 :documentation
"Connect table of auxiliary data with the specified presentation project by creating a view.")
296 :documentation
"Name of auxiliary table, which may be in any database. It must have a geometry column.")
297 ("coordinates-column"
298 :type string
:initial-value
"the-geom"
299 :documentation
"Name of the geometry column in the auxiliary data table.")
301 :type string
:list t
:optional t
302 :documentation
"Name of a numeric column in the auxiliary data table. Repeat if necessary.")
304 :type string
:list t
:optional t
305 :documentation
"Name of a text column in the auxiliary data table. Repeat if necessary.")))
307 (defparameter *cli-user-options
*
309 :type string
:action
#'create-user-action
310 :documentation
"Create or update user (specified by their ID) of certain presentation projects.")
311 ("user-password" :type string
:documentation
"User's password.")
312 ("user-full-name" :type string
:documentation
"User's real name.")
314 :type string
:initial-value
"read"
315 :documentation
"User's permission on their projects. One of \"read\", \"write\", or \"admin\" where \"write\" is the same as \"read\" plus permission to add user points and delete them if written by same user; and \"admin\" is the same as \"write\" plus permission to delete points written by other users.")
316 ("presentation-project" :type string
:list t
:optional t
317 :documentation
"Presentation project the user is allowed to see. Repeat if necessary.")
319 :type string
:action
#'delete-user-action
320 :documentation
"Delete user.")
322 :type string
:optional t
:action
#'list-user-action
323 :documentation
"List the specified user with their presentation projects, or all users if no user is given.")))
325 (defparameter *cli-options
*
326 (append *cli-general-options
*
327 *cli-db-connection-options
* *cli-aux-db-connection-options
*
328 *cli-get-image-options
*
329 *cli-camera-hardware-options
* *cli-lens-options
*
330 *cli-generic-device-options
* *cli-device-stage-of-life-options
*
331 *cli-device-stage-of-life-end-options
*
332 *cli-camera-calibration-options
*
333 *cli-acquisition-project-options
*
334 *cli-store-images-and-points-options
*
335 *cli-start-server-options
*
336 *cli-presentation-project-options
*
337 *cli-aux-view-options
*
341 "The UNIX command line entry point."
346 :error
"~A ~:[~;[Backtrace follows]~&~A~]~&"
348 *log-lisp-backtraces-p
*
349 (trivial-backtrace:print-backtrace c
:output nil
))
350 (format *error-output
* "~A~&" c
)
351 #+sbcl
(sb-ext:quit
:unix-status
1)))
353 (lambda (c) (cl-log:log-message
:warning
"~A" c
))))
354 (cffi:use-foreign-library phoml
)
355 (compute-and-process-command-line-options *cli-options
*)))
357 (defun ignore-warnings (c) (declare (ignore c
)) (muffle-warning))
359 (defmacro with-cli-options
((&rest options
) &body body
)
360 "Evaluate body with options bound to the values of the respective
361 command line arguments. Elements of options may be either symbols or
362 lists shaped like (symbol default)."
363 `(destructuring-bind (&key
,@options
&allow-other-keys
)
364 (cli-remaining-options)
367 (defun cli-remaining-options ()
368 "Return current set of command line options as an alist, and a list
369 of the non-option arguments. In passing, set global variables
370 according to the --verbose option given."
373 (process-command-line-options
374 *cli-options
* *command-line-arguments
*))))
375 (destructuring-bind (&key verbose
&allow-other-keys
)
377 ;;(setf hunchentoot:*show-lisp-backtraces-p* (logbitp 12 verbose)) ;doesn't seem to exist
378 ;; obeyed by both hunchentoot and Phoros' own logging:
379 (setf hunchentoot
:*log-lisp-backtraces-p
* (logbitp 13 verbose
))
380 ;; necessary for (ps ... (debug-info ...)...):
381 (setf *use-multi-file-openlayers
* (logbitp 14 verbose
))
382 (setf *ps-print-pretty
* (logbitp 15 verbose
))
383 (setf *show-lisp-errors-p
* (logbitp 16 verbose
)))
384 (values-list options
)))
386 (defun cli-help-action (&rest rest
)
387 "Print --help message."
388 (declare (ignore rest
))
389 (flet ((show-help-section
390 (options-specification
392 &rest introduction-paragraphs
)
393 "Show on *standard-output* help on options-specification
394 preceded by header and introduction-paragraphs."
395 (format *standard-output
*
396 "~@[~2&_____~72,,,'_@<~A~>~]~
397 ~@[~{~& ~{~@<~% ~1,72:;~A~> ~}~}~]"
400 #'(lambda (paragraph)
401 (cl-utilities:split-sequence-if
402 #'(lambda (x) (or (eql #\Space x
)
405 :remove-empty-subseqs t
))
406 introduction-paragraphs
))
407 (show-option-help options-specification
)))
410 "~&Usage: phoros [options] ...~&~A"
411 (handler-bind ((warning #'ignore-warnings
))
412 (asdf:system-long-description
(asdf:find-system
:phoros
))))
414 *cli-general-options
*
417 *cli-db-connection-options
*
418 "Database Connection (necessary for most operations)")
420 *cli-aux-db-connection-options
*
421 "Auxiliary Database Connection"
422 "Connection parameters to the database containing auxiliary data.
423 Only needed for definition (--create-aux-view) and use (--server)
426 *cli-get-image-options
*
427 "Examine .pictures File"
428 "Useful mostly for debugging purposes.")
430 *cli-camera-hardware-options
*
431 "Camera Hardware Parameters (not including information on lens or
436 "Stored primarily for human consumption; not used in
437 photogrammetric calculations.")
439 *cli-generic-device-options
*
440 "Generic Device Definition"
441 "Basically, this is a particular camera fitted with a particular
444 *cli-device-stage-of-life-options
*
445 "Device Stage-Of-Life Definition"
446 "A stage-of-life of a generic device is a possibly unfinished
447 period of time during which the mounting constellation of the
448 generic device remains unchanged.")
450 *cli-device-stage-of-life-end-options
*
451 "Put An End To A Device's Stage-Of-Life"
452 "This should be done after any event that renders any portion of
453 the calibration data invalid. E.g.: accidental change of mounting
456 *cli-camera-calibration-options
*
457 "Camera Calibration Parameters")
459 *cli-acquisition-project-options
*
460 "Manage Acquisition Projects"
461 "An acquisition project is a set of measurements which share a
462 set of data tables and views all of which have names beginning
463 with dat-<acquisition-project-name>-.")
465 *cli-store-images-and-points-options
*
466 "Store Measure Data")
468 *cli-start-server-options
*
469 "Become A HTTP Presentation Server")
471 *cli-presentation-project-options
*
472 "Manage Presentation Projects"
473 "A presentation project is a set of measurements that can be
474 visited under a dedicated URL
475 \(http://<host>:<port>/phoros/<presentation-project>).
476 Its extent may or may not be equal to the extent of an
477 acquisition project.")
479 *cli-aux-view-options
*
480 "Connect A Presentation Project To A Table Of Auxiliary Data"
481 "Arbitrary data from tables not directly belonging to any Phoros
482 project can be connected to a presentation project by means of a
483 view which must be named
484 usr-<presentation-project-name>-aux-point and which must contain
485 the columns coordinates (geometry), aux-numeric (null or array of
486 numeric), and aux-text (null or array of text). The array
487 elements of both aux-numeric and aux-text of auxiliary points can
488 then be incorporated into neighbouring user points."
489 "In simple cases (auxiliary data from one table which has a
490 geometry column and some numeric and/or text columns), the
491 following options can be used to create such view.")
494 "Manage Presentation Project Users")))
496 (defun phoros-version (&key major minor revision
)
497 "Return version of this program, either one integer part as denoted by
498 the key argument, or the whole dotted string."
499 (let* ((version-string
500 (handler-bind ((warning #'ignore-warnings
))
501 (asdf:component-version
(asdf:find-system
:phoros
))))
503 (mapcar #'parse-integer
504 (cl-utilities:split-sequence
#\. version-string
))))
505 (cond (major (first version-components
))
506 (minor (second version-components
))
507 (revision (third version-components
))
508 (t version-string
))))
510 (defun cli-version-action (&rest rest
)
511 "Print --version message. TODO: OpenLayers, Proj4js version."
512 (declare (ignore rest
))
513 (with-cli-options (verbose)
518 "~&~A~&" (phoros-version)))
522 "~&~A version ~A~& ~A version ~A~& ~
523 Proj4 library: ~A~& PhoML version ~A~&"
524 (handler-bind ((warning #'ignore-warnings
))
525 (asdf:system-description
(asdf:find-system
:phoros
)))
526 (handler-bind ((warning #'ignore-warnings
))
527 (asdf:component-version
(asdf:find-system
:phoros
)))
528 (lisp-implementation-type) (lisp-implementation-version)
530 (phoml:get-version-number
))))))
532 (defun cli-licence-action (&rest rest
)
533 "Print --licence boilerplate."
534 (declare (ignore rest
))
536 *standard-output
* "~&~A~&"
537 (handler-bind ((warning #'ignore-warnings
))
538 (asdf:system-licence
(asdf:find-system
:phoros
)))))
540 (defun check-db-action (&rest rest
)
541 "Say `OK´ if database is accessible."
542 (declare (ignore rest
))
543 (with-cli-options (host (aux-host host
) port
(aux-port port
)
544 database
(aux-database database
)
545 (user "") (aux-user user
)
546 (password "") (aux-password password
)
547 use-ssl
(aux-use-ssl use-ssl
))
549 (check-db (list database user password host
551 :use-ssl
(s-sql:from-sql-name use-ssl
)))
552 (check-db (list aux-database aux-user aux-password aux-host
554 :use-ssl
(s-sql:from-sql-name aux-use-ssl
))))
555 (format *error-output
* "~&OK~%"))))
557 (defun check-dependencies-action (&rest rest
)
558 "Say `OK´ if the necessary external dependencies are available."
559 (declare (ignore rest
))
562 (geographic-to-utm 33 13 52) ;check cs2cs
563 (del-all) ;check photogrammetry
564 (initialize-leap-seconds) ;check source of leap second info
565 (format *error-output
* "~&OK~%"))
566 (error (e) (format *error-output
* "~A~&" e
))))
568 (defun nuke-all-tables-action (&rest rest
)
569 "Drop the bomb. Ask for confirmation first."
570 (declare (ignore rest
))
571 (with-cli-options (host port database
(user "") (password "") use-ssl
573 (launch-logger log-dir
)
575 "You asked me to delete anything in database ~A at ~A:~D. ~
578 (with-connection (list database user password host
:port port
579 :use-ssl
(s-sql:from-sql-name use-ssl
)) ; string to keyword
582 :db-sys
"Nuked database ~A at ~A:~D. Back to square one!"
583 database host port
))))
585 (defun create-sys-tables-action (&rest rest
)
586 "Make a set of sys-* tables. Ask for confirmation first."
587 (declare (ignore rest
))
588 (with-cli-options (host port database
(user "") (password "") use-ssl
590 (launch-logger log-dir
)
592 "You asked me to create a set of sys-* tables ~
593 in database ~A at ~A:~D. ~
594 Make sure you know what you are doing. Proceed?"
596 (with-connection (list database user password host
:port port
597 :use-ssl
(s-sql:from-sql-name use-ssl
))
600 :db-sys
"Created a fresh set of system tables in database ~A at ~A:~D."
601 database host port
))))
603 (defun create-acquisition-project-action (common-table-name)
604 "Make a set of data tables."
605 (with-cli-options (host port database
(user "") (password "") use-ssl
607 (launch-logger log-dir
)
608 (with-connection (list database user password host
:port port
609 :use-ssl
(s-sql:from-sql-name use-ssl
))
610 (create-acquisition-project common-table-name
))
613 "Created a fresh acquisition project by the name of ~A ~
614 in database ~A at ~A:~D."
615 common-table-name database host port
)))
617 (defun delete-acquisition-project-action (common-table-name)
618 "Delete an acquisition project."
619 (with-cli-options (host port database
(user "") (password "") use-ssl
621 (launch-logger log-dir
)
623 "You asked me to delete acquisition-project ~A ~
624 (including all its measurements) ~
625 from database ~A at ~A:~D. Proceed?"
626 common-table-name database host port
)
627 (with-connection (list database user password host
:port port
628 :use-ssl
(s-sql:from-sql-name use-ssl
))
629 (let ((project-did-exist-p
630 (delete-acquisition-project common-table-name
)))
633 "~:[Tried to delete nonexistent~;Deleted~] ~
634 acquisition project ~A from database ~A at ~A:~D."
635 project-did-exist-p common-table-name database host port
))))))
637 (defun delete-measurement-action (measurement-id)
638 "Delete a measurement by its measurement-id."
639 (with-cli-options (host port database
(user "") (password "") use-ssl
641 (launch-logger log-dir
)
642 (with-connection (list database user password host
:port port
643 :use-ssl
(s-sql:from-sql-name use-ssl
))
644 (let ((measurement-did-exist-p
645 (delete-measurement measurement-id
)))
648 "~:[Tried to delete nonexistent~;Deleted~] ~
649 measurement with ID ~A from database ~A at ~A:~D."
650 measurement-did-exist-p measurement-id database host port
)))))
652 (defun list-acquisition-project-action (&optional common-table-name
)
653 "List content of acquisition projects."
654 (with-cli-options (host port database
(user "") (password "") use-ssl
)
655 (with-connection (list database user password host
:port port
656 :use-ssl
(s-sql:from-sql-name use-ssl
))
658 (if (stringp common-table-name
)
663 'sys-acquisition-project.acquisition-project-id
668 'sys-acquisition-project
:natural
:left-join
'sys-measurement
669 :where
(:= 'common-table-name common-table-name
))
675 'sys-acquisition-project.acquisition-project-id
680 'sys-acquisition-project
:natural
:left-join
'sys-measurement
)
681 'common-table-name
'measurement-id
)))))
683 *standard-output
* " | " content
684 "Acquisition Project" "ID" "Meas. ID" "Directory" "Cartesian CS")))))
686 (defun store-images-and-points-action (common-table-name)
687 "Put data into the data tables."
688 (with-cli-options (host port database
(user "") (password "") use-ssl
690 directory epsilon common-root aggregate-events
)
691 (launch-logger log-dir
)
692 (with-connection (list database user password host
:port port
693 :use-ssl
(s-sql:from-sql-name use-ssl
))
696 "Start: storing data from ~A into acquisition project ~A ~
697 in database ~A at ~A:~D."
698 directory common-table-name database host port
)
699 (store-images-and-points common-table-name directory
700 :epsilon
(read-from-string epsilon nil
)
701 :root-dir common-root
702 :aggregate-events aggregate-events
))
705 "Finish: storing data from ~A into acquisition project ~A ~
706 in database ~A at ~A:~D."
707 directory common-table-name database host port
)))
709 ;;; We don't seem to have two-dimensional arrays in postmodern
710 ;;(defun canonicalize-bayer-pattern (raw &optional sql-string-p)
711 ;; "Convert list of strings of comma-separated hex color strings (ex: #ff0000 for red) into an array of integers. If sql-string-p is t, convert it into a string in SQL syntax."
718 ;; for hex-color in (cl-utilities:split-sequence #\, row)
720 ;; (let ((*read-base* 16))
721 ;; (assert (eql (elt hex-color 0) #\#) () "~A is not a valid color" hex-color)
723 ;; (concatenate 'string
724 ;; (subseq hex-color 5 7)
725 ;; (subseq hex-color 3 5)
726 ;; (subseq hex-color 1 3))
728 ;; (rows (length array))
729 ;; (columns (length (elt array 0))))
731 ;; (format nil "{~{{~{~A~#^,~}}~}}" array)
732 ;; (make-array (list rows columns) :initial-contents array)))))
734 (defun canonicalize-bayer-pattern (raw &optional sql-string-p
)
735 "Convert a string of comma-separated hex color strings (ex: #ff0000
736 for red) into a vector of integers. If sql-string-p is t, convert it
737 into a string in SQL syntax."
741 for hex-color in
(cl-utilities:split-sequence
#\
, raw
)
743 (let ((*read-base
* 16))
744 (assert (eql (elt hex-color
0) #\
#)
745 () "~A is not a valid color" hex-color
)
748 (subseq hex-color
5 7)
749 (subseq hex-color
3 5)
750 (subseq hex-color
1 3))
752 (columns (length vector
)))
754 (format nil
"{~{~A~#^,~}}" vector
)
755 (make-array (list columns
) :initial-contents vector
)))))
757 (defun canonicalize-color-raiser (raw &optional sql-string-p
)
758 "Convert string of comma-separated numbers into a vector. If
759 sql-string-p is t, convert it into a string in SQL syntax."
763 for multiplier in
(cl-utilities:split-sequence
#\
, raw
:count
3)
765 (read-from-string multiplier nil
))))
767 (format nil
"{~{~A~#^,~}}" vector
)
768 (make-array '(3) :initial-contents vector
)))))
770 (defun store-stuff (store-function)
771 "Open database connection and call store-function on command line
772 options. Print return values to *standard-output*. store-function
773 should only take keyargs."
774 (let ((command-line-options
775 (cli-remaining-options)))
776 (setf (getf command-line-options
:bayer-pattern
)
777 (canonicalize-bayer-pattern
778 (getf command-line-options
:raw-bayer-pattern
) t
)
779 (getf command-line-options
:color-raiser
)
780 (canonicalize-color-raiser
781 (getf command-line-options
:raw-color-raiser
) t
))
782 (destructuring-bind (&key host port database
(user "") (password "") use-ssl
783 log-dir
&allow-other-keys
)
785 (launch-logger log-dir
)
786 (with-connection (list database user password host
:port port
787 :use-ssl
(s-sql:from-sql-name use-ssl
))
788 (format *standard-output
* "~&~{~D~#^ ~}~%"
790 (apply store-function
:allow-other-keys t
791 command-line-options
)))))))
793 (defun store-camera-hardware-action (&rest rest
)
794 (declare (ignore rest
))
795 (store-stuff #'store-camera-hardware
))
797 (defun store-lens-action (&rest rest
)
798 (declare (ignore rest
))
799 (store-stuff #'store-lens
))
801 (defun store-generic-device-action (&rest rest
)
802 (declare (ignore rest
))
803 (store-stuff #'store-generic-device
))
805 (defun store-device-stage-of-life-action (&rest rest
)
806 (declare (ignore rest
))
807 (store-stuff #'store-device-stage-of-life
))
809 (defun store-device-stage-of-life-end-action (&rest rest
)
810 (declare (ignore rest
))
811 (store-stuff #'store-device-stage-of-life-end
))
813 (defun store-camera-calibration-action (&rest rest
)
814 (declare (ignore rest
))
815 (store-stuff #'store-camera-calibration
))
817 (defun get-image-action (&rest rest
)
818 "Output a PNG file extracted from a .pictures file; print its
819 trigger-time to stdout."
820 (declare (ignore rest
))
821 (with-cli-options (count byte-position in out
822 raw-bayer-pattern raw-color-raiser
)
823 (with-open-file (out-stream out
:direction
:output
824 :element-type
'unsigned-byte
825 :if-exists
:supersede
)
828 (send-png out-stream in byte-position
830 (canonicalize-bayer-pattern raw-bayer-pattern
)
832 (canonicalize-color-raiser raw-color-raiser
))
833 (send-nth-png count out-stream in
835 (canonicalize-bayer-pattern raw-bayer-pattern
)
837 (canonicalize-color-raiser raw-color-raiser
)))))
838 (format *standard-output
*
839 "~&~A~%" (timestring (utc-from-unix trigger-time
)))))))
841 (defun create-presentation-project-action (presentation-project-name)
842 "Make a presentation project."
843 (with-cli-options (host port database
(user "") (password "") use-ssl
845 (launch-logger log-dir
)
846 (with-connection (list database user password host
:port port
847 :use-ssl
(s-sql:from-sql-name use-ssl
))
848 (let ((fresh-project-p
849 (create-presentation-project presentation-project-name
)))
852 "~:[Tried to recreate an existing~;Created a fresh~] ~
853 presentation project by the name of ~A in database ~A at ~A:~D."
854 fresh-project-p presentation-project-name database host port
)))))
857 (defun delete-presentation-project-action (presentation-project-name)
858 "Delete a presentation project."
859 (with-cli-options (host port database
(user "") (password "") use-ssl
861 (launch-logger log-dir
)
863 "You asked me to delete presentation-project ~A ~
864 (including its table of user-defined points usr-~:*~A-point) ~
865 from database ~A at ~A:~D. Proceed?"
866 presentation-project-name database host port
)
867 (with-connection (list database user password host
:port port
868 :use-ssl
(s-sql:from-sql-name use-ssl
))
869 (let ((project-did-exist-p
870 (delete-presentation-project presentation-project-name
)))
873 "~:[Tried to delete nonexistent~;Deleted~] ~
874 presentation project ~A from database ~A at ~A:~D."
875 project-did-exist-p presentation-project-name
876 database host port
))))))
878 (defun add-to-presentation-project-action (presentation-project-name)
879 "Add measurements to a presentation project."
880 (with-cli-options (host port database
(user "") (password "") use-ssl
882 measurement-id acquisition-project
)
883 (launch-logger log-dir
)
884 (with-connection (list database user password host
:port port
885 :use-ssl
(s-sql:from-sql-name use-ssl
))
886 (add-to-presentation-project presentation-project-name
887 :measurement-ids measurement-id
888 :acquisition-project acquisition-project
))
891 "Added ~@[measurement-ids ~{~D~#^, ~}~]~
892 ~@[all measurements from acquisition project ~A~] ~
893 to presentation project ~A in database ~A at ~A:~D."
894 measurement-id acquisition-project
895 presentation-project-name database host port
)))
897 (defun remove-from-presentation-project-action (presentation-project-name)
898 "Add measurements to a presentation project."
899 (with-cli-options (host port database
(user "") (password "") use-ssl
901 measurement-id acquisition-project
)
902 (launch-logger log-dir
)
903 (with-connection (list database user password host
:port port
904 :use-ssl
(s-sql:from-sql-name use-ssl
))
905 (remove-from-presentation-project presentation-project-name
906 :measurement-ids measurement-id
907 :acquisition-project acquisition-project
))
910 "Removed ~@[measurement-ids ~{~D~#^, ~}~]~
911 ~@[all measurements that belong to acquisition project ~A~] ~
912 from presentation project ~A in database ~A at ~A:~D."
913 measurement-id acquisition-project
914 presentation-project-name database host port
)))
916 (defun create-aux-view-action (presentation-project-name)
917 "Connect presentation project to an auxiliary data table by means of
919 (with-cli-options (host (aux-host host
) port
(aux-port port
)
920 database
(aux-database database
)
921 (user "") (aux-user user
)
922 (password "") (aux-password password
)
923 use-ssl
(aux-use-ssl use-ssl
)
925 aux-table coordinates-column
926 numeric-column text-column
)
927 (launch-logger log-dir
)
928 (with-connection (list aux-database aux-user aux-password aux-host
930 :use-ssl
(s-sql:from-sql-name aux-use-ssl
))
931 (let ((aux-view-in-phoros-db-p
933 (list host port database user password use-ssl
)
934 (list aux-host aux-port aux-database
935 aux-user aux-password aux-use-ssl
)))
937 (aux-view-exists-p presentation-project-name
)))
939 aux-view-in-phoros-db-p
941 "I'm going to ~:[create~;replace~] a view named ~A ~
942 in database ~A at ~A:~D. Proceed?"
944 (aux-point-view-name presentation-project-name
)
945 aux-database aux-host aux-port
))
946 (when aux-view-exists-p
947 (delete-aux-view presentation-project-name
))
948 (handler-bind ((warning #'ignore-warnings
)) ;TODO: muffle more postgresql warnings
950 presentation-project-name aux-table
951 :coordinates-column
(s-sql:to-sql-name coordinates-column
)
952 :numeric-columns numeric-column
953 :text-columns text-column
))
956 "~:[Created~;Updated~] in database ~A at ~A:~D a view called ~A ~
957 into table (of auxiliary data) ~A. Coordinates column is ~A. ~
958 ~:[No numeric columns.~;Numeric column(s): ~:*~{~A~#^, ~}.~] ~
959 ~:[No text columns.~;Text column(s): ~:*~{~A~#^, ~}.~]"
961 aux-database aux-host aux-port
962 (aux-point-view-name presentation-project-name
)
963 aux-table coordinates-column
964 numeric-column text-column
))))))
966 (defun create-user-action (presentation-project-user)
969 (with-cli-options (host port database
(user "") (password "") use-ssl
971 user-password user-full-name
972 user-role presentation-project
)
973 (launch-logger log-dir
)
974 (with-connection (list database user password host
:port port
975 :use-ssl
(s-sql:from-sql-name use-ssl
))
977 (create-user presentation-project-user
978 :password user-password
979 :full-name user-full-name
981 :presentation-projects presentation-project
)))
983 :db-dat
;TODO: We're listing nonexistent p-projects here as well.
984 "~:[Updated~;Created~] user ~A (~A) who has ~A access ~
985 to ~:[no ~;~]presentation project(s)~:*~{ ~A~#^,~} ~
986 in database ~A at ~A:~D."
987 fresh-user-p presentation-project-user
988 user-full-name user-role
989 presentation-project database host port
))))
991 (defun delete-user-action (presentation-project-user)
992 "Delete a presentation project user."
993 (with-cli-options ( host port database
(user "") (password "") use-ssl
995 (launch-logger log-dir
)
996 (with-connection (list database user password host
:port port
997 :use-ssl
(s-sql:from-sql-name use-ssl
))
998 (let ((user-did-exist-p
999 (delete-user presentation-project-user
)))
1002 "~:[Tried to delete nonexistent~;Deleted~] ~
1003 presentation project user ~A from database ~A at ~A:~D."
1004 user-did-exist-p presentation-project-user database host port
)))))
1006 (defun list-user-action (&optional presentation-project-user
)
1007 "List presentation project users together with their presentation
1009 (with-cli-options (host port database
(user "") (password "") use-ssl
)
1010 (with-connection (list database user password host
:port port
1011 :use-ssl
(s-sql:from-sql-name use-ssl
))
1013 (if (stringp presentation-project-user
)
1017 'user-name
'sys-user.user-id
'user-password
1018 'user-full-name
'presentation-project-name
1019 'sys-user-role.presentation-project-id
'user-role
1020 :from
'sys-user
'sys-user-role
'sys-presentation-project
1021 :where
(:and
(:= 'sys-user-role.presentation-project-id
1022 'sys-presentation-project.presentation-project-id
)
1023 (:= 'sys-user.user-id
'sys-user-role.user-id
)
1024 (:= 'user-name presentation-project-user
)))
1029 'user-name
'sys-user.user-id
'user-password
1030 'user-full-name
'presentation-project-name
1031 'sys-user-role.presentation-project-id
'user-role
1032 :from
'sys-user
'sys-user-role
'sys-presentation-project
1033 :where
(:and
(:= 'sys-user-role.presentation-project-id
1034 'sys-presentation-project.presentation-project-id
)
1035 (:= 'sys-user.user-id
'sys-user-role.user-id
)))
1038 *standard-output
* " | " content
1039 "User" "ID" "Password" "Full Name" "Presentation Project" "ID" "Role")))))
1041 (defun list-presentation-project-action (&optional presentation-project
)
1042 "List content of presentation projects."
1043 (with-cli-options (host port database
(user "") (password "") use-ssl
)
1044 (with-connection (list database user password host
:port port
1045 :use-ssl
(s-sql:from-sql-name use-ssl
))
1047 (if (stringp presentation-project
)
1051 'presentation-project-name
1052 'sys-presentation-project.presentation-project-id
1053 'sys-presentation.measurement-id
1055 'sys-measurement.acquisition-project-id
1057 'sys-presentation-project
'sys-presentation
1058 'sys-measurement
'sys-acquisition-project
1059 :where
(:and
(:= 'sys-presentation-project.presentation-project-id
1060 'sys-presentation.presentation-project-id
)
1061 (:= 'sys-presentation.measurement-id
1062 'sys-measurement.measurement-id
)
1063 (:= 'sys-measurement.acquisition-project-id
1064 'sys-acquisition-project.acquisition-project-id
)
1065 (:= 'presentation-project-name presentation-project
)))
1066 'presentation-project-name
'sys-presentation.measurement-id
))
1070 'presentation-project-name
1071 'sys-presentation-project.presentation-project-id
1072 'sys-presentation.measurement-id
1074 'sys-measurement.acquisition-project-id
1076 'sys-presentation-project
'sys-presentation
1077 'sys-measurement
'sys-acquisition-project
1078 :where
(:and
(:= 'sys-presentation-project.presentation-project-id
1079 'sys-presentation.presentation-project-id
)
1080 (:= 'sys-presentation.measurement-id
1081 'sys-measurement.measurement-id
)
1082 (:= 'sys-measurement.acquisition-project-id
1083 'sys-acquisition-project.acquisition-project-id
)))
1084 'presentation-project-name
'sys-presentation.measurement-id
)))))
1085 (format-table *standard-output
* " | " content
1086 "Presentation Project" "ID" "Meas. ID" "Acquisition Project" "ID")))))
1088 (defun format-table (destination column-separator content
&rest column-headers
)
1089 "Print content (a list of lists) to destination."
1091 (append (list column-headers
) (list ()) content
))
1092 (number-of-rows (length column-headers
))
1095 for column from
0 below number-of-rows collect
1098 maximize
(length (format nil
"~A" (nth column row
)))))))
1101 for width in widths collect
1102 (make-string width
:initial-element
#\-
)))
1105 (format destination
"~&~{~VA~1,#^~A~}~%"
1107 for width in widths and field in row
1108 collect width collect field collect column-separator
)))))
1110 (defun server-action (&rest rest
)
1111 "Start the HTTP server."
1112 (declare (ignore rest
))
1113 (with-cli-options (host (aux-host host
) port
(aux-port port
)
1114 database
(aux-database database
)
1115 (user "") (aux-user user
)
1116 (password "") (aux-password password
)
1117 use-ssl
(aux-use-ssl use-ssl
)
1119 http-port address common-root
)
1120 (launch-logger log-dir
)
1121 (setf *postgresql-credentials
*
1122 (list database user password host
:port port
1123 :use-ssl
(s-sql:from-sql-name use-ssl
)))
1124 (setf *postgresql-aux-credentials
*
1125 (list aux-database aux-user aux-password aux-host
:port aux-port
1126 :use-ssl
(s-sql:from-sql-name aux-use-ssl
)))
1127 (start-server :http-port http-port
:address address
1128 :common-root common-root
)
1131 "HTTP server listens on port ~D ~
1132 of ~:[all available addresses~;address ~:*~A~]. ~
1133 Phoros database is ~A on ~A:~D. Auxiliary database is ~A on ~A:~D. ~
1134 Files are searched for in ~A."
1137 aux-database aux-host aux-port