1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2010, 2011, 2017 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.
21 (defun nuke-all-tables ()
22 "Drop all tables (except PostGIS helper tables) and sequences in current database. TODO: also drop our functions and types."
23 (let ((user-tables (list-tables))
24 (sequences (list-sequences))
26 (system-tables '(:spatial-ref-sys
:geometry-columns
)))
27 (dolist (system-table system-tables
)
28 (setf user-tables
(remove system-table user-tables
)))
29 (dolist (user-table user-tables
)
30 (execute (format nil
"DROP TABLE IF EXISTS ~A CASCADE" (s-sql:to-sql-name user-table
))))
31 (dolist (sequence sequences
)
32 (execute (format nil
"DROP SEQUENCE IF EXISTS ~A CASCADE" (s-sql:to-sql-name sequence
))))
34 (execute (format nil
"DROP VIEW IF EXISTS ~A CASCADE" (s-sql:to-sql-name view
))))))
36 ;;; Used only to add Spherical Mercator in case it's missing
37 (defclass spatial-ref-sys
()
42 :col-type
(or db-null
(varchar 256))
45 :col-type
(or db-null integer
)
48 :col-type
(or db-null
(varchar 2048))
51 :col-type
(or db-null
(varchar 2048))
53 (:metaclass dao-class
)
55 (:documentation
"PostGIS system table as defined in http://postgis.refractions.net/documentation/manual-1.3/ch04.html#id2571306"))
57 ;;TODO: make a spatial-ref-sys table
59 (defun add-spherical-mercator-ref ()
60 "Tell PostGIS about Spherical Mercator if necessary."
61 (let ((spherical-mercator
65 :auth-name
"spatialreferencing.org"
67 :srtext
"PROJCS[\"Popular Visualisation CRS / Mercator (deprecated)\",GEOGCS[\"Popular Visualisation CRS\",DATUM[\"Popular_Visualisation_Datum\",SPHEROID[\"Popular Visualisation Sphere\",6378137,0,AUTHORITY[\"EPSG\",\"7059\"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",\"6055\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4055\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"3785\"],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]]"
68 :proj4text
"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +units=m +k=1.0 +nadgrids=@null +no_defs")))
69 (unless (dao-exists-p spherical-mercator
)
70 (insert-dao spherical-mercator
))))
72 ;;TODO: make a geometry-columns table
74 ;;CREATE TABLE geometry_columns (
75 ;; f_table_catalog VARRCHAR(256) NOT NULL,
76 ;; f_table_schema VARCHAR(256) NOT NULL,
77 ;; f_table_nam VARCHAR(256) NOT NULL,
78 ;; f_geometry_column VARCHAR(256) NOT NULL,
79 ;; coord_dimension INTEGER NOT NULL,
80 ;; srid INTEGER NOT NULL,
81 ;; type VARCHAR(30) NOT NULL
89 :col-default
(:nextval
'sys-user-id-seq
))
93 :documentation
"This one is used for authentication.")
95 :writer
(setf user-password
)
97 :initarg
:user-password
)
99 :writer
(setf user-full-name
)
101 :initarg
:user-full-name
))
102 (:metaclass dao-class
)
104 (:documentation
"List of users of the presentation front end. This is certainly not a full-fledged authentication system."))
107 (:create-sequence
'sys-user-id-seq
)
110 (defclass sys-acquisition-project
()
111 ((acquisition-project-id
112 :reader acquisition-project-id
114 :col-default
(:nextval
'sys-acquisition-project-id-seq
))
117 :initarg
:common-table-name
118 :documentation
"Name of this project's data tables sans their canonical prefixes and suffixes. Serves as a human-readable acquisition procect identifier. Should be one table for all projects but this seems to come with a speed penalty."))
119 (:metaclass dao-class
)
120 (:keys acquisition-project-id
)
121 (:documentation
"An acquisition project is basically a set of measurements that is stored in a common table."))
123 (deftable sys-acquisition-project
124 (:create-sequence
'sys-acquisition-project-id-seq
)
126 (:alter-table sys-acquisition-project
127 :add
:constraint
"common-table-name-unique"
128 :unique
'common-table-name
))
130 (defclass sys-presentation-project
()
131 ((presentation-project-id
132 :reader presentation-project-id
134 :col-default
(:nextval
'sys-presentation-project-id-seq
))
135 (presentation-project-name
137 :initarg
:presentation-project-name
)
139 :col-type
(or db-null text
)
140 :accessor bounding-box
141 :documentation
"Extent of this presentation project."))
142 (:metaclass dao-class
)
143 (:keys presentation-project-name
))
145 (deftable sys-presentation-project
146 (:create-sequence
'sys-presentation-project-id-seq
)
148 (:alter-table sys-presentation-project
149 :add
:constraint
"presentation-project-id-unique"
150 :unique
'presentation-project-id
))
152 (defclass sys-selectable-restriction
()
155 :initarg
:restriction-id
156 :documentation
"Short descriptive string; to be used for selection of restriction on client.")
157 (presentation-project-id
159 :initarg
:presentation-project-id
160 :documentation
"Presentation Project that is allowed to use the sql-clause.")
165 :documentation
"SQL clause suitable as an AND clause in aggregate view."))
166 (:metaclass dao-class
)
167 (:keys presentation-project-id restriction-id
)
168 (:documentation
"User-selectable SQL AND clauses usable in the WHERE clause of aggregate view."))
170 (deftable sys-selectable-restriction
172 (!foreign
'sys-presentation-project
'presentation-project-id
:on-delete
:cascade
:on-update
:cascade
))
174 (defclass sys-user-role
()
178 (presentation-project-id
179 :initarg
:presentation-project-id
184 :documentation
"One of read, write, admin.")
186 :col-type
(or db-null text
)
187 :accessor bounding-box
188 :documentation
"Streetmap zoom extent last time user left Phoros.")
190 :col-type
(or db-null geometry
)
192 :documentation
"Point; user's work coordinate in streetmap last time they left Phoros."))
193 (:metaclass dao-class
)
194 (:keys user-id presentation-project-id
))
196 (deftable sys-user-role
198 (!foreign
'sys-user
'user-id
:on-delete
:cascade
:on-update
:cascade
)
199 (!foreign
'sys-presentation-project
'presentation-project-id
200 :on-delete
:cascade
:on-update
:cascade
))
202 (defclass sys-measurement
()
204 :reader measurement-id
206 :col-default
(:nextval
'sys-measurement-id-seq
))
207 (acquisition-project-id
208 :initarg
:acquisition-project-id
214 "Below some universal root common to all measurements; excluding
215 `applanix/´ `images/´ etc.
217 The entire directory structure looks like this:
221 /some/path/in/our/system/this/measurement/blah/applanix/points/xyz-event1.txt
222 /some/path/in/our/system/this/measurement/blah/applanix/points/uvw-event2.txt
223 ---- +++++++++++++++ ----+++++ ++++
224 ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^
225 universal root stored here in event
226 slot directory number
230 /some/path/in/our/system/this/measurement/blah/images/front77.pictures
231 /some/path/in/our/system/this/measurement/blah/images/front78.pictures
233 ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
234 universal root stored here in stored file name
238 ---- means unimportant
240 TODO: /images/ part not currently enforced.")
242 :initarg
:cartesian-system
245 "Definition (as a Proj.4 string) of the coordinate system the recorded standard deviations are in."))
246 (:metaclass dao-class
)
247 (:keys measurement-id
)
248 (:documentation
"A measurement comprises .pictures files and one set of GPS event log files in a dedicated directory."))
250 (deftable sys-measurement
251 (:create-sequence
'sys-measurement-id-seq
)
253 (!index
'measurement-id
)
254 (!foreign
'sys-acquisition-project
'acquisition-project-id
255 :on-delete
:cascade
:on-update
:cascade
))
257 (defclass sys-presentation
()
258 ((presentation-project-id
259 :initarg
:presentation-project-id
262 :initarg
:measurement-id
264 (:metaclass dao-class
)
265 (:keys presentation-project-id measurement-id
)
266 (:documentation
"Tell us which measurements belong to which presentation project(s)."))
268 (deftable sys-presentation
270 (!index
'measurement-id
)
271 (!foreign
'sys-presentation-project
'presentation-project-id
272 :on-delete
:cascade
:on-update
:cascade
)
273 (!foreign
'sys-measurement
'measurement-id
274 :on-delete
:cascade
:on-update
:cascade
))
276 (defclass sys-camera-hardware
()
278 :reader camera-hardware-id
280 :col-default
(:nextval
'sys-camera-hardware-id-seq
))
286 :col-type double-float
)
293 :documentation
"Array of multipliers for red, green, blue.")
296 :documentation
"Array containing the colors of the first pixels of the first rows (actually, row, as postmodern can't handle two-dimensional arrays (and if it could, we wouldn't use them anyway)). Each pixel is to be interpreted as a three-byte RGB value, red in the least-significant byte.")
301 :description
"Camera type, manufacturer, etc."))
302 (:metaclass dao-class
)
303 (:keys camera-hardware-id
))
305 (deftable sys-camera-hardware
306 (:create-sequence
'sys-camera-hardware-id-seq
)
308 (!index
'camera-hardware-id
))
310 (defclass sys-lens
()
314 :col-default
(:nextval
'sys-lens-id-seq
))
316 :col-type double-float
317 :documentation
"Focal length. Only for human consumption.")
322 :documentation
"Lens type, manufacturer, etc."))
323 (:metaclass dao-class
)
327 (:create-sequence
'sys-lens-id-seq
)
330 (defclass sys-generic-device
()
332 :reader generic-device-id
334 :col-default
(:nextval
'sys-generic-device-id-seq
))
336 :initarg
:camera-hardware-id
337 :col-type
(or db-null integer
))
340 :col-type
(or db-null integer
))
343 :col-type
(or db-null integer
)
344 :documentation
"Scanners yet to be defined."))
345 (:metaclass dao-class
)
346 (:keys generic-device-id
)
347 (:documentation
"A row should describe either a camera with a lens, or a laser scanner."))
349 (deftable sys-generic-device
350 (:create-sequence
'sys-generic-device-id-seq
)
352 (!index
'generic-device-id
)
353 (!index
'camera-hardware-id
)
354 (!foreign
'sys-camera-hardware
'camera-hardware-id
355 :on-delete
:restrict
:on-update
:restrict
)
356 (!foreign
'sys-lens
'lens-id
:on-delete
:restrict
:on-update
:restrict
)
357 ;;;; Once we have a sys-scanner table:
358 ;;(!foreign 'sys-scanner 'scanner-id :on-delete :restrict :on-update :restrict)
361 (defclass sys-device-stage-of-life
()
362 ((device-stage-of-life-id
363 :reader device-stage-of-life-id
365 :col-default
(:nextval
'sys-device-stage-of-life-seq
))
368 :documentation
"Must be stored next to each data record. Example: in a .pictures file, this is the value of `cam=´.")
372 :documentation
"Identifier for the GPS event that triggers this device. Must correspond to the N the GPS file name: ...eventN.txt.")
379 :documentation
"Something like `upper rear left´ or maybe `1.2.1´")
382 :documentation
"Computer (or or other recording device) this device is connected to.")
383 (computer-interface-name
385 :documentation
"Things like `eth0´, `COM1´ etc.")
387 :col-type
:timestamp-with-time-zone
)
389 :col-type
(or db-null
:timestamp-with-time-zone
)
390 :documentation
"Date and time when this device was unmounted or altered in other ways that may render the current calibration invalid."))
391 (:metaclass dao-class
)
392 (:keys device-stage-of-life-id
)
393 (:documentation
"This data is to be collected on the measuring vehicle. There must be a new record for every relevant change (including accidental ones) in device configuration."))
395 (deftable sys-device-stage-of-life
396 (:create-sequence
'sys-device-stage-of-life-seq
)
398 (!index
'device-stage-of-life-id
)
399 (!index
'recorded-device-id
)
400 (!index
'mounting-date
)
401 (!index
'unmounting-date
)
402 (!foreign
'sys-generic-device
'generic-device-id
403 :on-delete
:restrict
:on-update
:restrict
))
405 (defclass sys-camera-calibration
()
406 ((device-stage-of-life-id
407 :reader device-stage-of-life-id
409 :documentation
"This tells us what hardware this calibration is for.")
412 :col-type
:timestamp-with-time-zone
)
417 :documentation
"Regarding this entire set of calibration data. Note the special-purpose description fields inner-orientation-description, outer-orientation-description, boresight-description.")
420 :documentation
"If false: just display images, don't perform photogrammetric calculations.")
423 :documentation
"If true: not for production use; may be altered or deleted at any time.")
424 (photogrammetry-version
426 :documentation
"Software version used to create this data.")
429 :documentation
"Head up = 0; right ear up = 90; left ear up = -90; head down = 180.")
430 (inner-orientation-description
432 :documentation
"Comments regarding inner orientation calibration.")
434 :col-type double-float
435 :documentation
"Inner orientation: focal length.")
437 :col-type double-float
438 :documentation
"Inner orientation: principal point displacement.")
440 :col-type double-float
441 :documentation
"Inner orientation: principal point displacement.")
443 :col-type double-float
444 :documentation
"Inner orientation: radial distortion.")
446 :col-type double-float
447 :documentation
"Inner orientation: radial distortion.")
449 :col-type double-float
450 :documentation
"Inner orientation: radial distortion.")
452 :col-type double-float
453 :documentation
"Inner orientation: asymmetric and tangential distortion.")
455 :col-type double-float
456 :documentation
"Inner orientation: asymmetric and tangential distortion.")
458 :col-type double-float
459 :documentation
"Inner orientation: affinity and shear distortion.")
461 :col-type double-float
462 :documentation
"Inner orientation: affinity and shear distortion.")
464 :col-type double-float
465 :documentation
"Inner orientation.")
466 (outer-orientation-description
468 :documentation
"Comments regarding outer orientation calibration.")
470 :col-type double-float
471 :documentation
"Outer orientation; in metres.")
473 :col-type double-float
474 :documentation
"Outer orientation; in metres.")
476 :col-type double-float
477 :documentation
"Outer orientation; in metres.")
479 :col-type double-float
480 :documentation
"Outer orientation.")
482 :col-type double-float
483 :documentation
"Outer orientation.")
485 :col-type double-float
486 :documentation
"Outer orientation.")
487 (boresight-description
489 :documentation
"Comments regarding boresight alignment calibration.")
491 :col-type double-float
492 :documentation
"Boresight alignment.")
494 :col-type double-float
495 :documentation
"Boresight alignment.")
497 :col-type double-float
498 :documentation
"Boresight alignment.")
500 :col-type double-float
501 :documentation
"Boresight alignment.")
503 :col-type double-float
504 :Documentation
"Boresight alignment.")
506 :col-type double-float
507 :documentation
"Boresight alignment.")
509 :col-type double-float
510 :documentation
"Boresight alignment.")
512 :col-type double-float
513 :documentation
"Boresight alignment.")
515 :col-type double-float
516 :documentation
"Boresight alignment.")
518 :col-type double-float
519 :documentation
"Boresight alignment.")
521 :col-type double-float
522 :documentation
"Boresight alignment.")
524 :col-type double-float
525 :documentation
"Boresight alignment.")
527 :col-type double-float
528 :documentation
"Component of unit vector of vehicle ground plane.")
530 :col-type double-float
531 :documentation
"Component of unit vector of vehicle ground plane.")
533 :col-type double-float
534 :documentation
"Component of unit vector of vehicle ground plane.")
536 :col-type double-float
537 :documentation
"Distance of vehicle ground plane."))
538 (:metaclass dao-class
)
539 (:keys device-stage-of-life-id date
)
540 (:documentation
"Camera calibration parameters."))
542 (deftable sys-camera-calibration
544 (!index
'device-stage-of-life-id
)
546 (!foreign
'sys-device-stage-of-life
'device-stage-of-life-id
547 :on-delete
:restrict
:on-update
:restrict
))
549 (defun phoros-db-major-version ()
550 "Get the version number of the database structure we are currently
551 connected to. It is supposed to be equal to the :major part of
554 (:select
'last-value
:from
'sys-phoros-major-version
)
557 (defun (setf phoros-db-major-version
) (major-version)
558 "Set the version number of the database structure we are currently
559 connected to. This can only be done once per database."
561 (:create-sequence
'sys-phoros-major-version
563 :max-value major-version
564 :start major-version
))
565 (phoros-db-major-version))
567 (defun assert-phoros-db-major-version ()
568 "Check if phoros version and version of the database we are
570 (assert (= (phoros-db-major-version) (phoros-version :major t
)) ()
571 "Can't use a Phoros database structure of version ~D. ~
572 It should be version ~D. ~
573 Either create a new database structure using this version of ~
574 Phoros, or use Phoros version ~2:*~D.x.x."
575 (phoros-db-major-version) (phoros-version :major t
)))
577 (defun assert-presentation-project (presentation-project-name)
578 "Signal error if presentation-project-name can't be found in current
580 (presentation-project-id-from-name presentation-project-name
))
582 (defun presentation-project-id-from-name (presentation-project-name)
583 "Get from current database the presentation-project-id associated
584 with presentation-project-name. Signal error if there isn't any."
585 (let ((presentation-project
586 (get-dao 'sys-presentation-project presentation-project-name
)))
587 (assert presentation-project
()
588 "There is no presentation project called ~A."
589 presentation-project-name
)
590 (presentation-project-id presentation-project
)))
592 (defun assert-acquisition-project (acquisition-project-name)
593 "Signal error if acquisition-project-name can't be found in current
595 (assert (select-dao 'sys-acquisition-project
596 (:= 'common-table-name acquisition-project-name
))
598 "There is no acquisition project called ~A."
599 acquisition-project-name
))
601 (defun create-sys-tables ()
602 "Create in current database a set of sys-* tables, i.e. tables that
603 are used by all projects. The database should probably be empty."
604 (setf (phoros-db-major-version) (phoros-version :major t
))
605 (create-table 'sys-user
)
606 (create-table 'sys-acquisition-project
)
607 (create-table 'sys-presentation-project
)
608 (create-table 'sys-selectable-restriction
)
609 (create-table 'sys-user-role
)
610 (create-table 'sys-measurement
)
611 (create-table 'sys-presentation
)
612 (create-table 'sys-camera-hardware
)
613 (create-table 'sys-lens
)
614 (create-table 'sys-generic-device
)
615 (create-table 'sys-device-stage-of-life
)
616 (create-table 'sys-camera-calibration
)
617 (create-plpgsql-helpers))
619 (defun create-plpgsql-helpers ()
620 "Create in current database a few SQL types and functions."
625 (point1 GEOMETRY, point2 GEOMETRY, point3 GEOMETRY)
626 RETURNS DOUBLE PRECISION AS $$
629 RETURN abs(st_azimuth(point2, point3) - st_azimuth(point1, point2));
631 $$ LANGUAGE plpgsql;"
634 "DROP TYPE IF EXISTS point_bag;")
636 "CREATE TYPE point_bag AS (id int, coordinates GEOMETRY);"))
638 (defun !!index
(table field
&key
(index-type :btree
))
639 (format nil
"CREATE INDEX ~0@*~A_~1@*~A_index ON ~0@*~A USING ~2@*~A (~1@*~A)"
640 (s-sql:to-sql-name table
)
641 (s-sql:to-sql-name field
)
642 (s-sql:to-sql-name index-type
)))
644 (defclass point-template
()
645 (;; We need a slot point-id which is defined in our subclasses.
648 :initform
(random (expt 2 31))
649 :documentation
"Used for quickly getting an evenly distributed sample of all points.")
651 :writer
(setf measurement-id
)
654 :documentation
"Event that triggered this record. Taken from the GPS file name: ...eventN.txt gives an event number N. May be a string of any length.")
657 :documentation
"UTC calculated from GPS week time.")
659 :writer
(setf trigger-time
)
660 :col-type double-precision
661 :documentation
"Time in seconds from 1900. Values before 1980-01-06T00:00:00Z are considered invalid.")
663 :col-type double-precision
)
665 :col-type double-precision
)
667 :col-type double-precision
)
669 :col-type double-precision
)
671 :col-type double-precision
)
673 :col-type double-precision
)
675 :col-type double-precision
)
677 :col-type double-precision
)
679 :col-type double-precision
)
681 :col-type double-precision
)
683 :col-type double-precision
)
685 :col-type double-precision
)
688 :documentation
"Same content as in slot coordinates.")
691 :documentation
"Same content as in slot coordinates.")
693 :reader ellipsoid-height
694 :documentation
"Same content as in slot coordinates.")
696 :col-type
(or db-null geometry
)
697 :documentation
"Geographic coordinates.")
700 :documentation
"In the same coordinate system as the standard deviations.")
703 :documentation
"In the same coordinate system as the standard deviations.")
705 :reader cartesian-height
706 :documentation
"In the same coordinate system as the standard deviations."))
707 (:metaclass dao-class
)
709 (:documentation
"Information about one GPS point, originally from applanix/**/*event*.txt. There shouldn't be any point-id without a matching one in the *-image table. This can't be enforced on database level. Use (delete-imageless-points acquisition-project) to maintain referential integrity."))
711 (defclass image-template
()
713 :writer
(setf measurement-id
)
715 :documentation
"A primary key. We need to recognize images should they come in twice, perhaps with slightly changed point data. In such a case we want the old ones superseded.")
720 :documentation
"Name without any directory components.")
722 :reader image-byte-position
723 :initarg
:byte-position
725 :documentation
"Start of image in .pictures file named by slot filename.")
730 :initarg
:recorded-device-id
731 :reader recorded-device-id
733 :documentation
"As found in .pictures file, header tag `cam=´.")
736 :col-type
(or db-null geometry
)
737 :documentation
"Polygon on the ground describing the approximate area covered by this image.")
738 (footprint-device-stage-of-life-id
739 :initarg
:footprint-device-stage-of-life-id
740 :col-type
(or db-null integer
)
741 :documentation
"device-stage-of-life denoting the set of calibration data the footprint of this record has been calculated with.")
744 :col-type double-precision
745 :documentation
"Camera parameter. TODO: needs a decent definition")
748 :col-type double-precision
749 :documentation
"Camera parameter. TODO: needs a decent definition")
751 :initarg
:trigger-time
752 :accessor trigger-time
753 :documentation
"Time in seconds from 1900-01-01.")
755 :accessor fake-trigger-time-p
757 :documentation
"T if trigger-time has been reconstructed from adjacent data.")
759 :initarg
:camera-timestamp
760 :reader camera-timestamp
761 :documentation
"Some camera clocktick count starting at an unknown origin."))
762 (:metaclass dao-class
)
763 (:keys measurement-id filename byte-position
)
764 (:documentation
"One row per image, originating from a .pictures file."))
766 (defclass user-point-template
()
767 (;; We need a slot user-point-id which is defined in our subclasses.
770 :col-type
(or db-null
;when store-user-points is fed an unknown user-name
772 :documentation
"User who stored this point.")
776 :documentation
"Class of this user point.")
778 :initarg
:description
780 :documentation
"User comment regarding this point.")
782 :initarg
:numeric-description
784 :documentation
"User-generated point id regarding this point.")
786 :col-type
:timestamp-with-time-zone
787 :documentation
"Creation time of this point.")
789 :col-type
(or db-null geometry
)
790 :documentation
"Geographic coordinates.")
792 ;; :initarg :stdx-global
793 ;; :col-type double-precision
794 ;; :documentation "Component of standard deviation, in metres.")
796 ;; :initarg :stdy-global
797 ;; :col-type double-precision
798 ;; :documentation "Component of standard deviation, in metres.")
800 ;; :initarg :stdz-global
801 ;; :col-type double-precision
802 ;; :documentation "Component of standard deviation, in metres.")
806 :documentation
"Number of points (from different images) used for calculation.")
808 :col-type
(or db-null numeric
[])
809 :documentation
"Arbitrary numeric values from auxiliary point table.")
811 :col-type
(or db-null text
[])
812 :documentation
"Arbitrary text values from auxiliary point table."))
813 (:metaclass dao-class
)
814 (:keys user-point-id
)
815 (:documentation
"Points defined by users."))
817 (defclass point-data
(point-template)
822 :col-default nil
) ;to be redefined
823 point-id-sequence-name
) ;to be redefined
824 (:metaclass dao-class
)
825 (:table-name nil
)) ;to be redefined
827 (defclass image-data
(image-template)
829 (:metaclass dao-class
)
830 (:table-name nil
)) ;to be redefined
832 (defclass user-point-data
(user-point-template)
834 :accessor user-point-id
837 :col-default nil
) ;to be redefined
838 user-point-id-sequence-name
) ;to be redefined)
839 (:metaclass dao-class
)
840 (:table-name nil
)) ;to be redefined
842 (let ((table-prefix "dat-"))
843 (defun point-data-table-name (common-table-name)
844 (make-symbol (format nil
"~A~A-point"
845 table-prefix common-table-name
)))
847 (defun image-data-table-name (common-table-name)
848 (make-symbol (format nil
"~A~A-image"
849 table-prefix common-table-name
)))
851 (defun point-id-seq-name (common-table-name)
852 (make-symbol (format nil
"~A~A-point-id-seq"
853 table-prefix common-table-name
)))
855 (defun aggregate-view-name (common-table-name)
856 (make-symbol (format nil
"~A~A-aggregate"
857 table-prefix common-table-name
)))
859 (defun aggregate-view-update-rule-name (common-table-name)
860 (make-symbol (format nil
"~A~A-aggregate-update"
861 table-prefix common-table-name
))))
863 (let ((table-prefix "usr-"))
864 (defun user-point-table-name (presentation-project-name)
865 (make-symbol (format nil
"~A~A-point"
866 table-prefix presentation-project-name
)))
868 (defun user-point-id-seq-name (presentation-project-name)
869 (make-symbol (format nil
"~A~A-point-id-seq"
870 table-prefix presentation-project-name
)))
872 (defun user-line-table-name (presentation-project-name)
873 (make-symbol (format nil
"~A~A-line"
874 table-prefix presentation-project-name
))))
876 (let ((table-prefix "phoros-"))
877 ;; This stuff may reside in a foreign database so we show explicitly
878 ;; what it belongs to.
879 (defun aux-point-view-name (presentation-project-name)
880 (make-symbol (format nil
"~A~A-aux-point"
881 table-prefix presentation-project-name
)))
883 (defun thread-aux-points-function-name (presentation-project-name)
884 (make-symbol (format nil
"~A~A-thread-aux-points"
885 table-prefix presentation-project-name
))))
887 (defun create-data-table-definitions (common-table-name)
888 "Define or redefine a bunch of dao-classes which can hold measuring
889 data and which are connected to database tables named
890 common-table-name plus type-specific prefix and suffix."
891 (let ((image-data-table-name
892 (image-data-table-name common-table-name
))
893 (point-data-table-name
894 (point-data-table-name common-table-name
))
895 (point-id-sequence-name
896 (point-id-seq-name common-table-name
)))
898 `(defclass point-data
(point-template)
903 :col-default
(:nextval
,point-id-sequence-name
)) ; redefinition
904 (point-id-sequence-name
905 :initform
,(string point-id-sequence-name
) ; redefinition
906 :reader point-id-sequence-name
908 (:metaclass dao-class
)
909 (:table-name
,point-data-table-name
))) ;redefinition
911 (:create-sequence point-id-sequence-name
)
913 (!!index point-data-table-name
'random
)
914 (!!index point-data-table-name
'measurement-id
)
915 (!!index point-data-table-name
'trigger-time
)
916 (!!index point-data-table-name
'coordinates
:index-type
:gist
)
917 (!!index point-data-table-name
'point-id
)
918 ;; The following let shouldn't be necessary. (Wart In !foreign.)
919 (let ((*table-symbol
* point-data-table-name
)
920 (*table-name
* (s-sql:to-sql-name point-data-table-name
)))
921 (!foreign
'sys-measurement
'measurement-id
922 :on-delete
:cascade
:on-update
:cascade
)))
924 `(defclass image-data
(image-template)
926 (:metaclass dao-class
)
927 (:table-name
,image-data-table-name
))) ; redefintion
930 (!!index image-data-table-name
'measurement-id
)
931 (!!index image-data-table-name
'recorded-device-id
)
932 (!!index image-data-table-name
'point-id
)
933 ;; (!!index image-data-table-name 'gain)
934 ;; (!!index image-data-table-name 'shutter)
935 (!!index image-data-table-name
'footprint
:index-type
:gist
)
936 ;; The following let shouldn't be necessary. (Wart in !foreign.)
937 (let ((*table-symbol
* image-data-table-name
)
938 (*table-name
* (s-sql:to-sql-name image-data-table-name
)))
939 (!foreign point-data-table-name
'point-id
940 :on-delete
:cascade
:on-update
:cascade
)
941 (!foreign
'sys-measurement
'measurement-id
942 :on-delete
:cascade
:on-update
:cascade
)))))
944 (defun create-user-table-definition (presentation-project-name)
945 "Define or redefine a dao-class which can hold user points and which
946 is connected to a database table named presentation-project-name plus
947 type-specific prefix and suffix."
948 (let ((user-point-table-name
949 (user-point-table-name presentation-project-name
))
950 (user-point-id-sequence-name
951 (user-point-id-seq-name presentation-project-name
)))
953 `(defclass user-point
(user-point-template)
958 :col-default
(:nextval
,user-point-id-sequence-name
))) ; redefinition
959 (:metaclass dao-class
)
960 (:table-name
,user-point-table-name
))) ;redefinition
962 (:create-sequence user-point-id-sequence-name
)
964 (!!index user-point-table-name
'coordinates
:index-type
:gist
))))
966 (defun create-aggregate-view (common-table-name)
967 "Create a view of a set of measuring and calibration data
968 belonging to images."
969 (let ((image-data-table-name (image-data-table-name common-table-name
))
970 (point-data-table-name (point-data-table-name common-table-name
))
971 (aggregate-view-name (aggregate-view-name common-table-name
))
972 (aggregate-view-update-rule-name (aggregate-view-update-rule-name
979 'sys-device-stage-of-life.recorded-device-id
;debug
980 'sys-device-stage-of-life.device-stage-of-life-id
;debug
981 'sys-device-stage-of-life.generic-device-id
;debug
983 'presentation-project-id
985 (:dot
',image-data-table-name
'measurement-id
)
986 'filename
'byte-position
987 (:dot
',point-data-table-name
'point-id
)
988 'footprint
'footprint-device-stage-of-life-id
990 'coordinates
;the search target
991 (:as
(:st_x
(:st_transform
'coordinates
*standard-coordinates
*))
993 (:as
(:st_y
(:st_transform
'coordinates
*standard-coordinates
*))
995 (:as
(:st_z
(:st_transform
'coordinates
*standard-coordinates
*))
998 'east-sd
'north-sd
'height-sd
999 'roll
'pitch
'heading
'roll-sd
'pitch-sd
'heading-sd
1001 'sensor-width-pix
'sensor-height-pix
'pix-size
1002 'bayer-pattern
'color-raiser
1004 'dx
'dy
'dz
'omega
'phi
'kappa
1005 'c
'xh
'yh
'a1
'a2
'a3
'b1
'b2
'c1
'c2
'r0
1006 'b-dx
'b-dy
'b-dz
'b-rotx
'b-roty
'b-rotz
1007 'b-ddx
'b-ddy
'b-ddz
'b-drotx
'b-droty
'b-drotz
1012 ',point-data-table-name
',image-data-table-name
1013 'sys-device-stage-of-life
'sys-generic-device
'sys-camera-hardware
1014 'sys-camera-calibration
1017 (:= (:dot
',image-data-table-name
'measurement-id
)
1018 'sys-presentation.measurement-id
)
1019 (:= 'sys-presentation.measurement-id
1020 'sys-measurement.measurement-id
)
1021 (:= (:dot
',point-data-table-name
'point-id
)
1022 (:dot
',image-data-table-name
'point-id
))
1023 (:= (:dot
',image-data-table-name
'recorded-device-id
)
1024 'sys-device-stage-of-life.recorded-device-id
)
1025 (:= 'sys-generic-device.generic-device-id
1026 'sys-device-stage-of-life.generic-device-id
)
1027 (:= 'sys-camera-hardware.camera-hardware-id
1028 'sys-generic-device.camera-hardware-id
)
1029 (:= 'sys-device-stage-of-life.device-stage-of-life-id
1030 'sys-camera-calibration.device-stage-of-life-id
)
1031 (:= 'sys-device-stage-of-life.device-stage-of-life-id
1034 (:select
'sys-camera-calibration.device-stage-of-life-id
1035 :from
'sys-camera-calibration
1037 (:= 'sys-device-stage-of-life.device-stage-of-life-id
1038 'sys-camera-calibration.device-stage-of-life-id
))
1041 (:<= (:extract
:epoch
'sys-device-stage-of-life.mounting-date
)
1042 (:-
(:dot
',point-data-table-name
'trigger-time
)
1044 (:or
(:is-null
'sys-device-stage-of-life.unmounting-date
)
1045 (:>= (:extract
:epoch
'sys-device-stage-of-life.unmounting-date
)
1046 (:-
(:dot
',point-data-table-name
'trigger-time
)
1047 *unix-epoch
*))))))))
1051 "CREATE OR REPLACE RULE ~A ~
1052 AS ON UPDATE TO ~A DO INSTEAD ~
1054 SET footprint = NEW.footprint, ~
1055 footprint_device_stage_of_life_id = OLD.device_stage_of_life_id
1056 WHERE byte_position = OLD.byte_position ~
1057 AND filename = OLD.filename ~
1058 AND measurement_id = OLD.measurement_id;"
1059 (s-sql:to-sql-name aggregate-view-update-rule-name
)
1060 (s-sql:to-sql-name aggregate-view-name
)
1061 (s-sql:to-sql-name image-data-table-name
)))))
1063 (defun aux-view-exists-p (presentation-project-name)
1064 "See if there is a view into auxiliary point table that belongs to
1065 presentation-project-name."
1066 (view-exists-p (aux-point-view-name presentation-project-name
)))
1068 (defun delete-aux-view (presentation-project-name)
1069 "Delete the view into auxiliary point table that belongs to
1070 presentation-project-name."
1071 (execute (format nil
"DROP VIEW ~A CASCADE;"
1072 (s-sql:to-sql-name
(aux-point-view-name
1073 presentation-project-name
))))
1075 (format nil
"DROP FUNCTION IF EXISTS ~
1076 ~A(GEOMETRY, DOUBLE PRECISION, INT, DOUBLE PRECISION);"
1077 (s-sql:to-sql-name
(thread-aux-points-function-name
1078 presentation-project-name
)))))
1080 (defun* create-aux-view
(presentation-project-name
1081 &key
(coordinates-column :the-geom
)
1082 numeric-columns text-columns
1083 &mandatory-key aux-table
)
1084 "Create a view into aux-table and an SQL function for threading
1085 aux-points into a linestring. coordinates-column goes into column
1086 coordinates, numeric-columns and text-columns go into arrays in
1087 aux-numeric and aux-text respectively.
1089 aux-table should have an index like so:
1091 CREATE INDEX idx_<aux-table>_the_geom
1093 USING gist (the_geom);
1095 VACUUM FULL ANALYZE <aux-table> (the_geom);"
1096 (create-plpgsql-helpers)
1097 (flet ((to-sql-name-or-null (name)
1099 (s-sql:to-sql-name name
)
1101 (let ((aux-point-view-name
1102 (aux-point-view-name presentation-project-name
))
1103 (thread-aux-points-function-name
1104 (thread-aux-points-function-name presentation-project-name
))
1107 (:select
(:as
(:select
(:count t
)
1108 :from
(make-symbol aux-table
)
1109 :where
(:<> (:st_srid
(make-symbol coordinates-column
))
1110 *standard-coordinates
*))
1112 (:as
(:select
(:count
(make-symbol coordinates-column
))
1113 :from
(make-symbol aux-table
))
1116 (unless (zerop (getf srid-count
:bad
))
1117 (warn "In column ~A of auxiliary data table ~A, ~D out of ~D values ~
1118 have currently an unsuitable SRID not equal to ~D."
1119 coordinates-column aux-table
1120 (getf srid-count
:bad
) (getf srid-count
:total
)
1121 *standard-coordinates
*))
1122 (execute (format nil
"
1124 AS (SELECT ~A AS coordinates,
1125 ~:[NULL~;ARRAY[~:*~{~A~#^, ~}]~] AS aux_numeric,
1126 ~:[NULL~;ARRAY[~:*~{~A~#^, ~}]~] AS aux_text
1128 (s-sql:to-sql-name aux-point-view-name
)
1129 (s-sql:to-sql-name coordinates-column
)
1130 (mapcar #'to-sql-name-or-null numeric-columns
)
1131 (mapcar #'to-sql-name-or-null text-columns
)
1132 (s-sql:to-sql-name aux-table
)))
1133 (execute (format nil
"~
1134 CREATE OR REPLACE FUNCTION ~0@*~A
1135 (point GEOMETRY, sample_radius DOUBLE PRECISION, sample_size INT,
1136 step_size DOUBLE PRECISION, old_azimuth DOUBLE PRECISION,
1137 max_bend DOUBLE PRECISION,
1138 OUT threaded_points TEXT,
1139 OUT current_point TEXT,
1140 OUT back_point TEXT, OUT forward_point TEXT,
1141 OUT new_azimuth DOUBLE PRECISION)
1144 -- Phoros version ~2@*~A
1147 current_point_position DOUBLE PRECISION;
1148 location DOUBLE PRECISION;
1150 new_point point_bag%ROWTYPE;
1151 tried_point point_bag%ROWTYPE;
1152 previous_point point_bag%ROWTYPE;
1153 starting_point GEOMETRY;
1154 reversal_count INT DEFAULT 0;
1156 -- Muffle warnings about implicitly created stuff:
1157 SET client_min_messages TO ERROR;
1165 st_setsrid(st_makebox3d (st_translate (point,
1166 - sample_radius * 5,
1167 - sample_radius * 5, 0),
1168 st_translate (point,
1170 sample_radius * 5, 0)),
1172 ORDER BY st_distance(coordinates, point)
1175 CREATE TEMPORARY TABLE point_bag
1176 (id SERIAL primary key, coordinates GEOMETRY)
1179 INSERT INTO point_bag (coordinates)
1186 st_setsrid(st_makebox3d (st_translate (starting_point,
1188 - sample_radius, 0),
1189 st_translate (starting_point,
1193 AND st_distance (coordinates, starting_point) < sample_radius
1194 ORDER BY st_distance (coordinates, starting_point)
1197 point_bag_size := (SELECT count(*) from point_bag);
1199 -- emergency point_bag:
1200 IF point_bag_size < 5
1202 DROP TABLE point_bag;
1203 CREATE TEMPORARY TABLE point_bag
1204 (id SERIAL primary key, coordinates GEOMETRY)
1206 INSERT INTO point_bag (coordinates)
1212 st_setsrid(st_makebox3d (st_translate (point,
1213 - sample_radius * 100,
1214 - sample_radius * 100, 0),
1215 st_translate (point,
1216 sample_radius * 100,
1217 sample_radius * 100, 0)),
1219 ORDER BY st_distance (coordinates, starting_point)
1221 starting_point := (SELECT coordinates FROM point_bag where id = 3);
1225 (SELECT ROW(id, coordinates)
1227 ORDER BY st_distance (point_bag.coordinates, starting_point)
1230 DELETE FROM point_bag WHERE id = previous_point.id;
1233 (SELECT ROW(id, coordinates)
1235 ORDER BY st_distance (point_bag.coordinates, previous_point.coordinates)
1238 line := st_makeline(previous_point.coordinates,
1239 new_point.coordinates);
1242 st_azimuth(previous_point.coordinates, new_point.coordinates);
1244 IF abs(new_azimuth - old_azimuth) > radians(90)
1246 abs(new_azimuth - old_azimuth) < radians(270)
1249 st_azimuth(new_point.coordinates, previous_point.coordinates);
1250 line := st_reverse(line);
1253 DELETE FROM point_bag WHERE id = new_point.id;
1256 previous_point.coordinates := st_pointn(line,1);
1259 (SELECT ROW(id, coordinates)
1261 ORDER BY st_distance (coordinates, previous_point.coordinates)
1264 EXIT WHEN new_point IS NULL;
1266 IF bendedness(st_pointn(line, 2), st_pointn(line, 1),
1267 new_point.coordinates)
1268 < bendedness(st_pointn(line, st_npoints(line) - 1),
1269 st_pointn(line, st_npoints(line)), new_point.coordinates)
1271 bendedness(st_pointn(line, 2), st_pointn(line, 1),
1272 new_point.coordinates)
1275 line := st_addpoint(line, new_point.coordinates, 0);
1276 DELETE FROM point_bag WHERE id = new_point.id;
1279 line := st_reverse(line);
1281 reversal_count := reversal_count + 1 ;
1283 DELETE FROM point_bag WHERE id = tried_point.id;
1285 tried_point := new_point;
1288 IF mod(reversal_count, 2) = 1
1290 line := st_reverse(line);
1293 current_point_position :=
1294 st_line_locate_point(line, point);
1297 st_astext(st_line_interpolate_point(line, current_point_position));
1299 location := (current_point_position - (step_size / st_length(line)));
1300 IF location < 0 THEN location := 0; END IF;
1303 st_astext(st_line_interpolate_point(line, location));
1305 location := (current_point_position + (step_size / st_length(line)));
1306 IF location > 0 THEN location := 1; END IF;
1309 st_astext(st_line_interpolate_point(line, location));
1311 threaded_points := st_astext(line);
1315 $$ LANGUAGE plpgsql;"
1316 (s-sql:to-sql-name thread-aux-points-function-name
)
1317 (s-sql:to-sql-name aux-point-view-name
)
1318 (phoros-version))))))
1320 (defun create-acquisition-project (common-table-name)
1321 "Create in current database a fresh set of canonically named tables.
1322 common-table-name should in most cases resemble the project name and
1323 will be stored in table sys-acquisition-project, field
1325 (create-data-table-definitions common-table-name
)
1326 (handler-case (create-sys-tables) ;Create system tables if necessary.
1327 (cl-postgres-error:syntax-error-or-access-violation
() nil
))
1328 (assert-phoros-db-major-version)
1329 (when (select-dao 'sys-acquisition-project
1330 (:= 'common-table-name common-table-name
))
1331 (error "There is already an acquisition project by the name of ~A."
1333 (create-table 'point-data
)
1334 (create-table 'image-data
)
1335 (create-aggregate-view common-table-name
)
1337 (make-instance 'sys-acquisition-project
1338 :common-table-name common-table-name
)))
1340 (defun delete-acquisition-project (common-table-name)
1341 "Delete the acquisition project that uses common-table-name. Return
1342 nil if there wasn't any."
1343 (assert-phoros-db-major-version)
1345 (car (select-dao 'sys-acquisition-project
1346 (:= 'common-table-name common-table-name
)))))
1348 (delete-dao project
)
1349 (execute (:drop-view
1350 :if-exists
(aggregate-view-name common-table-name
)))
1351 (execute (:drop-table
1352 :if-exists
(image-data-table-name common-table-name
)))
1353 (execute (:drop-table
1354 :if-exists
(point-data-table-name common-table-name
)))
1355 (execute (:drop-sequence
1356 :if-exists
(point-id-seq-name common-table-name
))))))
1358 (defun delete-measurement (measurement-id)
1359 "Delete measurement with measurement-id if any; return nil if not."
1360 (assert-phoros-db-major-version)
1361 (let ((measurement (get-dao 'sys-measurement measurement-id
)))
1362 (when measurement
(delete-dao measurement
))))
1364 (defun create-presentation-project (project-name)
1365 "Create a fresh presentation project in current database. Return
1366 dao if one was created, or nil if it existed already."
1367 (assert-phoros-db-major-version)
1368 (unless (get-dao 'sys-presentation-project project-name
)
1369 (create-user-table-definition project-name
)
1370 (create-table 'user-point
)
1371 (create-presentation-project-trigger-function project-name
)
1372 (execute (format nil
"DROP TRIGGER IF EXISTS ~A ON ~:*~A;"
1373 (s-sql:to-sql-name
(user-point-table-name project-name
))))
1374 (execute (format nil
"
1376 AFTER INSERT OR UPDATE OR DELETE
1378 FOR EACH ROW EXECUTE PROCEDURE ~:*~A();"
1379 (s-sql:to-sql-name
(user-point-table-name project-name
))))
1380 (execute (sql-compile
1381 `(:create-table
,(user-line-table-name project-name
)
1382 ((description :type text
)
1383 ;; description would be a nice primary
1384 ;; key if it wasn't for QGIS which
1386 (id :type serial
:primary-key t
)
1387 (line :type geometry
)))))
1388 (insert-dao (make-instance 'sys-presentation-project
1389 :presentation-project-name project-name
))))
1391 (defun create-presentation-project-trigger-function
1392 (presentation-project
1393 &optional
(plpgsql-body
1395 nil
" RAISE NOTICE 'trigger fired: ~A';"
1396 (s-sql:to-sql-name
(user-point-table-name
1397 presentation-project
))))
1398 &rest plpgsql-body-args
)
1399 "(Re)create in current database an SQL trigger function with
1400 plpgsql-body (a format string that uses plpgsql-body-args)."
1403 CREATE OR REPLACE FUNCTION ~A() RETURNS trigger
1407 ------------------------------------------
1408 -- Define your trigger actions below:
1409 ------------------------------------------
1411 ------------------------------------------
1412 -- End of your trigger action definitions.
1413 ------------------------------------------
1416 $$ LANGUAGE plpgsql;"
1417 (s-sql:to-sql-name
(user-point-table-name presentation-project
))
1419 plpgsql-body-args
)))
1421 (defun fire-presentation-project-trigger-function (presentation-project)
1422 "Tickle user point table of presentation-project so it fires its
1424 (let ((user-point-table (user-point-table-name presentation-project
)))
1426 (:update user-point-table
1427 :set
'user-point-id
'user-point-id
1428 :where
(:= 'user-point-id
1429 (:limit
(:select
'user-point-id
1430 :from user-point-table
) 1))))))
1432 (defun delete-presentation-project (project-name)
1433 "Delete the presentation project project-name. Return nil if there
1435 (assert-phoros-db-major-version)
1436 (let ((project (get-dao 'sys-presentation-project project-name
)))
1438 (delete-dao project
)
1440 (:drop-table
:if-exists
(user-point-table-name project-name
)))
1442 (:drop-sequence
:if-exists
(user-point-id-seq-name project-name
)))
1444 (:drop-table
:if-exists
(user-line-table-name project-name
))))))
1446 (defun postmodern-as-clauses (row-alist)
1447 "Make a list of constant :as clauses from query result row-alist.
1448 Alias names are the column names from row-alist prefixed by first-."
1450 for column in row-alist
1453 ,(intern (string (prefix-aggregate-view-column (car column
)))
1456 (defun prefix-aggregate-view-column (column-name)
1457 "Return a symbol named column-name, prefixed by first-."
1458 (make-symbol (concatenate 'string
1460 (string column-name
))))
1462 (defun some-internal-image-reference (sql-clause)
1463 "Return t if there are occurences of
1464 first-<something-from-*aggregate-view-columns*>, which act as
1465 references to the first image."
1467 for i in
*aggregate-view-columns
*
1470 (ppcre:create-scanner
1471 (s-sql:to-sql-name
(prefix-aggregate-view-column i
))
1472 :case-insensitive-mode
1476 (defun* create-image-attribute
(presentation-project-name
1477 &mandatory-key tag sql-clause
)
1478 "Store a boolean SQL expression into current database. Return SQL
1479 expression previously stored for presentation-project-name and tag if
1480 any; return nil otherwise. Second return value is the number of
1481 images covered by the SQL expression, and third return value is the
1482 total number of images in presentation project. Both second and third
1483 return value are nil if sql-clause contains references to the first
1485 (assert-phoros-db-major-version)
1486 (let* ((presentation-project-id
1487 (presentation-project-id-from-name presentation-project-name
))
1488 (old-selectable-restriction
1489 (get-dao 'sys-selectable-restriction presentation-project-id tag
))
1491 (common-table-names presentation-project-id
))
1492 (empty-presentation-project-p (null common-table-names
))
1493 (selected-restrictions-conjunction
1494 (sql-where-conjunction (list sql-clause
)))
1495 (arbitrary-image-query
1499 for common-table-name in common-table-names
1500 for aggregate-view-name
1501 = (aggregate-view-name common-table-name
)
1503 `(:limit
(:select
,@*aggregate-view-columns
*
1504 :from
',aggregate-view-name
)
1506 (internal-reference-p (some-internal-image-reference sql-clause
))
1507 (arbitrary-image (unless empty-presentation-project-p
1508 (query arbitrary-image-query
:alist
)))
1509 (counting-selected-query
1510 ;; Only useful as an SQL syntax check if sql-clause contains
1511 ;; internal references.
1519 for common-table-name in common-table-names
1520 for aggregate-view-name
1521 = (aggregate-view-name common-table-name
)
1524 (:as
(:count t
) 'count
)
1528 ,@(postmodern-as-clauses arbitrary-image
)
1530 :from
',aggregate-view-name
)
1531 'images-of-acquisition-project-plus-reference-image
)
1533 (:and
(:= 'presentation-project-id
1534 ,presentation-project-id
)
1535 (:raw
,selected-restrictions-conjunction
)))))
1536 'acquisition-project-image-counts
))))
1537 (counting-total-query
1544 for common-table-name in common-table-names
1545 for aggregate-view-name
1546 = (aggregate-view-name common-table-name
)
1549 (:as
(:count
'*) 'count
)
1550 :from
',aggregate-view-name
1551 :where
(:= 'presentation-project-id
1552 ,presentation-project-id
))))
1553 'acquisition-project-image-counts
))))
1554 (number-of-selected-images
1555 (if empty-presentation-project-p
1557 (query counting-selected-query
:single
!)))
1558 (total-number-of-images
1559 (unless internal-reference-p
;otherwise don't waste time
1560 (if empty-presentation-project-p
1562 (query counting-total-query
:single
!)))))
1563 (save-dao (make-instance 'sys-selectable-restriction
1564 :presentation-project-id presentation-project-id
1565 :restriction-id tag
:sql-clause sql-clause
))
1567 (when old-selectable-restriction
(sql-clause old-selectable-restriction
))
1568 (if internal-reference-p nil number-of-selected-images
)
1569 (if internal-reference-p nil total-number-of-images
))))
1571 (defun* delete-image-attribute
(presentation-project-name &mandatory-key tag
)
1572 "Delete SQL expression stored with tag under
1573 presentation-project-name from current database. Return the SQL
1574 expression deleted if there was any; return nil otherwise."
1575 (assert-phoros-db-major-version)
1576 (let ((selectable-restriction
1577 (get-dao 'sys-selectable-restriction
1578 (presentation-project-id-from-name presentation-project-name
)
1580 (when selectable-restriction
1581 (delete-dao selectable-restriction
)
1582 (sql-clause selectable-restriction
))))
1584 (defun* create-user
(name &key
1585 presentation-projects
1590 "Create a fresh user entry or update an existing one with matching
1591 name. Assign it presentation-projects, deleting any previously
1592 existing assignments."
1593 (assert-phoros-db-major-version)
1594 (assert (or (string-equal "read" user-role
)
1595 (string-equal "write" user-role
)
1596 (string-equal "admin" user-role
))
1598 "~A is not a valid user-role." user-role
)
1599 (let ((user (or (car (select-dao 'sys-user
(:= 'user-name name
)))
1600 (make-instance 'sys-user
:user-name name
)))
1602 (setf (user-password user
) user-password
1603 (user-full-name user
) user-full-name
)
1604 (setf fresh-user-p
(save-dao user
))
1605 (mapcar #'delete-dao
(select-dao 'sys-user-role
1606 (:= 'user-id
(user-id user
))))
1607 (dolist (presentation-project-name presentation-projects
)
1608 (let ((presentation-project
1609 (get-dao 'sys-presentation-project presentation-project-name
)))
1610 (if presentation-project
1614 :user-id
(user-id user
)
1615 :presentation-project-id
1616 (presentation-project-id presentation-project
)
1617 :user-role
(string-downcase user-role
))) ;TODO: we should be able to set role per presentation-project.
1619 "There is no presentation project ~A" presentation-project-name
))))
1622 (defun delete-user (user-name)
1623 "Delete user user-name if any; return nil if not."
1624 (assert-phoros-db-major-version)
1625 (let ((user (car (select-dao 'sys-user
(:= 'user-name user-name
)))))
1626 (when user
(delete-dao user
))))
1628 (defun add-to-presentation-project (presentation-project-name
1629 &key measurement-ids acquisition-project
)
1630 "Add to presentation project presentation-project-name either a list
1631 of measurements (with measurement-id) or all measurements currently in
1632 acquisition-project (denoted by its common-table-name)."
1633 (assert-phoros-db-major-version)
1634 (let* ((presentation-project
1635 (car (select-dao 'sys-presentation-project
1636 (:= 'presentation-project-name
1637 presentation-project-name
))))
1638 (presentation-project-id
1639 (presentation-project-id presentation-project
)))
1640 (flet ((add-measurement (measurement-id)
1641 "Add one measurement to the given presentation-project."
1642 (unless (get-dao 'sys-presentation
1643 presentation-project-id
1646 (make-instance 'sys-presentation
1647 :presentation-project-id presentation-project-id
1648 :measurement-id measurement-id
)))))
1649 (cond (measurement-ids (mapc #'add-measurement measurement-ids
))
1650 (acquisition-project
1656 :from
'sys-measurement
'sys-acquisition-project
1658 (:= 'sys-acquisition-project.common-table-name
1659 acquisition-project
)
1660 (:= 'sys-measurement.acquisition-project-id
1661 'sys-acquisition-project.acquisition-project-id
)))
1663 (add-measurement measurement-id
)))
1665 "Don't know what to add. ~
1666 Need either measurement-id or acquisition-project."))))
1667 (let* ((common-table-names
1668 (common-table-names presentation-project-id
))
1669 (presentation-project-bounding-box
1670 (ignore-errors ;for empty presentation project
1678 (:st_extent
'coordinates
)
1682 for common-table-name in common-table-names
1683 for point-table-name
1684 = (point-data-table-name common-table-name
)
1685 ;; would have been nice, was too slow:
1686 ;; = (aggregate-view-name common-table-name)
1690 :from
',point-table-name
1691 :natural
:left-join
'sys-presentation
1693 (:= 'presentation-project-id
1694 ,presentation-project-id
))))
1697 (when presentation-project-bounding-box
1698 (setf (bounding-box presentation-project
)
1699 presentation-project-bounding-box
))
1700 (update-dao presentation-project
))))
1702 (defun remove-from-presentation-project (presentation-project-name
1703 &key measurement-ids acquisition-project
)
1704 "Remove from presentation project presentation-project-name either a
1705 list of measurements (with measurement-id) or all measurements
1706 currently in acquisition-project with (denoted by its
1707 common-table-name). Return nil if there weren't anything to remove."
1708 (assert-phoros-db-major-version)
1709 (let* ((presentation-project
1710 (car (select-dao 'sys-presentation-project
1711 (:= 'presentation-project-name
1712 presentation-project-name
))))
1713 (presentation-project-id
1714 (Presentation-project-id presentation-project
)))
1715 (flet ((remove-measurement (measurement-id)
1719 (:and
(:= 'measurement-id measurement-id
)
1720 (:= 'presentation-project-id
1721 presentation-project-id
))))))
1722 (when measurement
(delete-dao measurement
)))))
1723 (cond (measurement-ids (mapc #'remove-measurement measurement-ids
))
1724 (acquisition-project
1730 :from
'sys-measurement
'sys-acquisition-project
1732 (:= 'sys-acquisition-project.common-table-name
1733 acquisition-project
)
1734 (:= 'sys-measurement.acquisition-project-id
1735 'sys-acquisition-project.acquisition-project-id
)))
1737 (remove-measurement measurement-id
)))
1739 "Don't know what to remove. ~
1740 Need either measurement-id or acquisition-project."))))))