Sanitize threads in fasttrack
[phoros.git] / fasttrack.lisp
blobaf8c5c775076944a1208ed7b8100d7121a186d75
1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2012, 2016 Bert Burgemeister
3 ;;;
4 ;;; This program is free software; you can redistribute it and/or modify
5 ;;; it under the terms of the GNU General Public License as published by
6 ;;; the Free Software Foundation; either version 2 of the License, or
7 ;;; (at your option) any later version.
8 ;;;
9 ;;; This program is distributed in the hope that it will be useful,
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;; GNU General Public License for more details.
13 ;;;
14 ;;; You should have received a copy of the GNU General Public License along
15 ;;; with this program; if not, write to the Free Software Foundation, Inc.,
16 ;;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 (in-package #:phoros-fasttrack)
21 (defparameter *phoros-version*
22 (asdf:component-version (asdf:find-system :phoros))
23 "Phoros version as defined in system definition.")
25 (cffi:define-foreign-library phoml
26 (:unix (:or "./libphoml.so"
27 "./phoml/lib/libphoml.so"))
28 (t (:default "libphoml")))
30 (setf *read-default-float-format* 'double-float)
32 (defparameter *photogrammetry-mutex* (bt:make-lock "photogrammetry"))
34 (defparameter *fasttrack-version*
35 (asdf:component-version (asdf:find-system :phoros))
36 "Fasttrack version as defined in system definition. TODO: enforce equality with *phoros-version*")
38 (defstruct (db-credentials (:type list)) database user password host (port-key :port :read-only t) (port 5432) (ssl-key :use-ssl :read-only t) (ssl :no) (modifiedp-key :modifiedp :read-only t) (modifiedp t) (table-key :table :read-only t) table (allow-other-keys-key :allow-other-keys :read-only t) (allow-other-keys-value t :read-only t))
40 (defvar *postgresql-road-network-credentials* (make-db-credentials)
41 "A list: (database user password host :port 5432 :use-ssl ssl-p.")
43 (defvar *postgresql-zeb-credentials* (make-db-credentials)
44 "A list: (database user password host :port 5432 use-ssl :ssl-p.")
46 (defvar *road-network-chart-configuration* nil
47 "Database columns selected for rendering.")
49 (defvar *zeb-chart-configuration* nil
50 "Database columns selected for rendering.")
52 (defvar *accidents-chart-configuration* (list nil nil nil)
53 "Accidents rendering parameters.")
55 (defvar *postgresql-accidents-credentials* (make-db-credentials)
56 "A list: (database user password host &key :port 5432 :use-ssl ssl-p.")
58 (defvar *station* nil
59 "Current station.")
61 (defvar *road-section* nil
62 "If there is a chart, we store a list of its parameters (table vnk
63 nnk road-section-length) here.")
65 (defvar *road-section-raw-data* (make-hash-table :size 97)
66 "Undigested selected row numbers from road section dialog")
68 (defvar *road-section-selection* '()
69 "Row numbers of the road sections selected for processing.")
71 (defvar *road-network-chart-raw-data* (make-hash-table :size 997 :test #'equal)
72 "Raw messages from the road network part of the chart dialog")
74 (defvar *accidents-chart-raw-data* (list nil nil nil)
75 "Undigested input from the accidents part of the chart dialog.")
77 (defvar *zeb-chart-raw-data* (make-hash-table :size 997 :test #'equal)
78 "Raw messages from road section dialog")
80 (defparameter *aggregate-view-columns*
81 (list 'usable
82 'recorded-device-id ;debug
83 'device-stage-of-life-id ;debug
84 'generic-device-id ;debug
85 'directory
86 'measurement-id
87 'filename 'byte-position 'point-id
88 'trigger-time
89 ;;'coordinates ;the search target
90 'longitude 'latitude 'ellipsoid-height
91 'cartesian-system
92 'east-sd 'north-sd 'height-sd
93 'roll 'pitch 'heading
94 'roll-sd 'pitch-sd 'heading-sd
95 'sensor-width-pix 'sensor-height-pix
96 'pix-size
97 'bayer-pattern 'color-raiser
98 'mounting-angle
99 'dx 'dy 'dz 'omega 'phi 'kappa
100 'c 'xh 'yh 'a1 'a2 'a3 'b1 'b2 'c1 'c2 'r0
101 'b-dx 'b-dy 'b-dz 'b-rotx 'b-roty 'b-rotz
102 'b-ddx 'b-ddy 'b-ddz
103 'b-drotx 'b-droty 'b-drotz)
104 "Most of the column names of aggregate-view.")
106 (defvar *phoros-cookies* nil
107 "Container for cookies sent by Phoros server")
109 (defvar *phoros-url* nil
110 "URL of the Phoros project currently in use.")
112 (defvar *phoros-credentials* '("user" "password")
113 "List of (user password) used for login at *phoros-url*.")
115 (defvar *cache-dir* '(:relative "cache"))
117 (defparameter *image-size* '(800 700)
118 "Image size in pixels in a list (width height).")
120 (defparameter *chart-height* 155
121 "Height of chart in pixels.")
123 (defparameter *chart-fringe* 20
124 "Lower, uncharted part of chart.")
126 (defparameter *chart-tail* 200
127 "Rightmost, uncharted part of chart.")
129 (defparameter *scale-distance* 40
130 "Horizontal distance between two scales.")
132 (defvar *rear-view-image-data* nil
133 "The currently displayed image.")
135 (defvar *front-view-image-data* nil
136 "The currently displayed image.")
138 (defvar *rear-view-image-arrow-coordinates* nil)
140 (defvar *front-view-image-arrow-coordinates* nil)
142 (defvar *show-rear-view-p* t)
144 (defvar *show-front-view-p* t)
146 (defvar *cruise-control* nil)
148 (defvar *rear-view-image-done* nil)
150 (defvar *front-view-image-done* nil)
152 (defvar *pipeglade-pid-file* "fasttrack-pipeglade.pid")
154 (defparameter *cursor-color* "orange"
155 "Color of cursor in both chart and images.")
157 (defparameter *big-step* 10
158 "Station increment/decrement.")
160 (defparameter *pipeglade-out-lock* (bt:make-lock))
161 (defparameter *pipeglade-out-fifo* "in.fifo")
162 (defparameter *pipeglade-in-fifo* "out.fifo")
164 (defun pipeglade-out (widget action &rest data)
165 "Send a pipeglade command to UI."
166 (bt:with-lock-held (*pipeglade-out-lock*)
167 (with-open-file (out *pipeglade-out-fifo*
168 :direction :output
169 :if-exists :append
170 :if-does-not-exist :error)
171 (format out "~A:~A~{ ~@[~A~]~}~%" widget action data))))
174 (defun ensure-hyphen-before-digit (symbol)
175 "Return symbol with hyphens inserted after each letter that is
176 followed by a digit. "
177 (intern
178 (coerce
179 (loop
180 with need-hyphen-before-next-digit-p
181 for c across (string symbol)
182 if (and need-hyphen-before-next-digit-p (digit-char-p c))
183 collect #\- and collect c and do (setf need-hyphen-before-next-digit-p nil)
184 else collect c and do (setf need-hyphen-before-next-digit-p nil)
186 if (alpha-char-p c) do (setf need-hyphen-before-next-digit-p t) end)
187 'string)))
190 (defmacro with-statusbar-message (message &body body)
191 "Push message to statusbar while body is executing."
192 `(unwind-protect
193 (progn
194 (pipeglade-out "statusbar" "push_id" (sxhash ,message) ,message)
195 ,@body)
196 (pipeglade-out "statusbar" "pop_id" (sxhash ,message))))
198 (defmacro with-spinner (spinner &body body)
199 "Let spinner spin while body is executing."
200 `(unwind-protect
201 (progn
202 (pipeglade-out ,spinner "start")
203 ,@body)
204 (pipeglade-out ,spinner "stop")))
206 (define-condition attention () ())
208 (defmacro defun-cached (name (&rest args) &body body &aux (doc ""))
209 "Define a function whose return value must be readibly printable, is
210 being read from a chache if possible, and is being cached if
211 necessary. The function defined has a secondary return value
212 cached-p. If function is called with :from-cache-only t, let it
213 return nil and nil if there is nothing cached. If function is
214 called with a :message keyarg, a pretty-printed version will be
215 shown as part of the statusbar message."
216 (when (stringp (car body))
217 (setf doc (car body))
218 (setf body (cdr body)))
219 (cl-utilities:with-unique-names (input-stream output-stream)
220 `(defun ,name (,@args &key from-cache-only create-fresh-cache message)
221 ,doc
222 (flet ((read-from-cache ()
223 (with-open-file (,input-stream (cache-file-name ',name ,@args)
224 :direction :input
225 :if-does-not-exist :error)
226 (values (read ,input-stream) t)))
227 (run-and-cache ()
228 (values (with-statusbar-message (format nil "populating cache [~(~A~)~@[ ~A~]]" ',name message)
229 (with-open-file (,output-stream (cache-file-name ',name ,@args)
230 :direction :output
231 :if-exists :supersede)
232 (prin1 (progn ,@body)
233 ,output-stream)))
234 nil)))
235 (ensure-directories-exist (cache-file-name ',name ,@args))
237 (handler-bind
238 ((file-error (lambda (c)
239 (invoke-restart 'restart-create-fresh-cache "FILE")))
240 (end-of-file (lambda (c)
241 (invoke-restart 'restart-create-fresh-cache "EOF"))))
242 (restart-case (if create-fresh-cache
243 (run-and-cache)
244 (read-from-cache))
245 (restart-create-fresh-cache (para)
246 (if from-cache-only
247 (values nil nil)
248 (,name ,@args :create-fresh-cache t :message message)))))))))
251 ;; (defmacro defun-cached (name (&rest args) &body body &aux (doc ""))
252 ;; "Define a function whose return value must be readibly printable, is
253 ;; being read from a chache if possible, and is being cached if
254 ;; necessary. The function defined has a secondary return value
255 ;; cached-p. If function is called with :from-cache-only t, let it
256 ;; return nil and nil if there is nothing cached."
257 ;; (when (stringp (car body))
258 ;; (setf doc (car body))
259 ;; (setf body (cdr body)))
260 ;; (cl-utilities:with-unique-names (input-stream output-stream)
261 ;; `(defun ,name (,@args &key from-cache-only)
262 ;; ,doc
263 ;; (ensure-directories-exist (cache-file-name ',name ,@args))
264 ;; (with-open-file (,input-stream (cache-file-name ',name ,@args)
265 ;; :direction :input
266 ;; :if-does-not-exist nil)
267 ;; (if ,input-stream
268 ;; (values (read ,input-stream) t)
269 ;; (values (unless from-cache-only
270 ;; (with-statusbar-message (format nil "populating cache (~A)" ',name)
271 ;; (with-open-file (,output-stream (cache-file-name ',name ,@args)
272 ;; :direction :output)
273 ;; (prin1 (progn ,@body)
274 ;; ,output-stream))))
275 ;; nil))))))
277 (defmacro image-worker (view-direction)
278 (let (global-station global-image-data global-image-arrow-coordinates global-image-done time-widget spinner-widget draw-widget img-widget)
279 (ecase view-direction
280 (:rear-view
281 (setf global-station '*station*)
282 (setf global-image-data '*rear-view-image-data*)
283 (setf global-image-arrow-coordinates '*rear-view-image-arrow-coordinates*)
284 (setf global-image-done '*rear-view-image-done*)
285 (setf time-widget "rear_view_time")
286 (setf spinner-widget "spinner_rearview")
287 (setf draw-widget "draw_rearview")
288 (setf img-widget "img_rearview"))
289 (:front-view
290 (setf global-station '*station*)
291 (setf global-image-data '*front-view-image-data*)
292 (setf global-image-arrow-coordinates '*front-view-image-arrow-coordinates*)
293 (setf global-image-done '*front-view-image-done*)
294 (setf time-widget "front_view_time")
295 (setf spinner-widget "spinner_frontview")
296 (setf draw-widget "draw_frontview")
297 (setf img-widget "img_frontview")))
299 (cl-utilities:with-unique-names (current-image-station
300 current-station
301 current-image-arrow-coordinates
302 station
303 image-data
304 image-arrow-coordinates
305 point-radius
306 image-filename
307 image-label-coordinates)
308 `(lambda ()
309 (let ((current-image-station)
310 (current-station)
311 (current-image-arrow-coordinates))
312 (loop
313 (let ((station ,global-station)
314 (image-data ,global-image-data)
315 (image-arrow-coordinates ,global-image-arrow-coordinates))
316 (cond
317 ((eql current-station station)
318 (bt:thread-yield))
319 ((equal current-image-arrow-coordinates image-arrow-coordinates)
320 (bt:thread-yield))
322 (unless (and (image-data-p image-data)
323 (eql (image-data-station image-data) current-image-station))
324 (pipeglade-out ,time-widget "set_text" (iso-time (when (image-data-p image-data) (image-data-trigger-time image-data))))
325 (handler-case
326 (with-spinner ,spinner-widget
327 (let ((image-filename (when image-data (namestring (download-image image-data)))))
328 (if image-filename
329 (progn
330 (pipeglade-out ,draw-widget "remove" 2)
331 (pipeglade-out ,img-widget "set_from_file" image-filename))
332 (pipeglade-out ,img-widget "set_from_file" "public_html/phoros-logo-background.png"))))
333 (phoros-server-error ())) ;ignore
334 (setf current-image-station (when (image-data-p image-data) (image-data-station image-data))))
335 (if (image-data-p image-data)
336 (let* ((point-radius 5)
337 (image-label-coordinates (ignore-errors
338 (list (- (first image-arrow-coordinates) point-radius)
339 (- (second image-arrow-coordinates) point-radius)))))
340 (pipeglade-out ,draw-widget "remove" 2)
341 (when image-arrow-coordinates
342 (pipeglade-out ,draw-widget "move_to" 2 (first image-arrow-coordinates) (second image-arrow-coordinates))
343 (pipeglade-out ,draw-widget "line_to" 2 (first (last image-arrow-coordinates 2)) (second (last image-arrow-coordinates 2)))
344 (pipeglade-out ,draw-widget "stroke" 2)
345 (pipeglade-out ,draw-widget "arc" 2 (first image-arrow-coordinates) (second image-arrow-coordinates) point-radius 0 360)
346 (pipeglade-out ,draw-widget "stroke" 2)
347 (pipeglade-out ,draw-widget "move_to" 2 (first image-label-coordinates) (second image-label-coordinates))
348 (pipeglade-out ,draw-widget "rel_move_for" 2 "se" (image-data-station image-data))
349 (pipeglade-out ,draw-widget "show_text" 2 station)))
350 (progn
351 (pipeglade-out ,draw-widget "remove" 2)))
352 (setf current-station station)
353 (setf current-image-arrow-coordinates image-arrow-coordinates)
354 (setf ,global-image-done t))))))))))
356 (eval '(defstruct coordinates
357 longitude
358 latitude
359 ellipsoid-height
360 azimuth))
362 (eval `(defstruct image-data
363 ;; fasttrack auxiliary slots
364 station
365 station-coordinates
366 (rear-view-p nil)
367 ;; original Phoros image data slots
368 ,@(mapcar #'ensure-hyphen-before-digit *aggregate-view-columns*)))
370 (defun start-pipeglade ()
371 (let* ((stale-pipeglade-pid
372 (with-open-file (stream *pipeglade-pid-file*
373 :direction :input :if-does-not-exist :create)
374 (read stream nil)))
375 (stale-pipeglade-program-name
376 (uiop:run-program (format nil "ps -p ~A -o comm=" stale-pipeglade-pid) :output :string :ignore-error-status t))
377 (length (min (length "pipeglade") (length stale-pipeglade-program-name))))
378 (when (string= "pipeglade" stale-pipeglade-program-name :end2 length)
379 (uiop:run-program (format nil "kill ~A" stale-pipeglade-pid))))
380 (let ((pipeglade-args "-i in.fifo -o out.fifo -u fasttrack.ui -b -l log.log --name fasttrack --class Phoros"))
381 (loop
382 for i in '("./pipeglade" "~/pipeglade/pipeglade" "pipeglade")
383 until (probe-file i)
384 finally (uiop:run-program (format nil "~A ~A" i pipeglade-args) :output *pipeglade-pid-file*))))
386 (defun version-number-parts (dotted-string)
387 "Return the three version number components of something like
388 \"11.22.33\"."
389 (when dotted-string
390 (values-list (mapcar #'parse-integer
391 (cl-utilities:split-sequence #\. dotted-string)))))
393 (defun fasttrack-version (&key major minor revision)
394 "Return version of this program, either one integer part as denoted by
395 the key argument, or the whole dotted string."
396 (multiple-value-bind (major-number minor-number revision-number)
397 (version-number-parts *fasttrack-version*)
398 (cond (major major-number)
399 (minor minor-number)
400 (revision revision-number)
401 (t *fasttrack-version*))))
403 (defun main ()
404 (handler-case
405 (progn
406 (in-package #:phoros-fasttrack) ;for reading of cached #S(...) forms
407 (cffi:use-foreign-library phoml)
408 (start-pipeglade)
409 (restore-road-network-credentials)
410 (restore-zeb-credentials)
411 (restore-accidents-credentials)
412 (restore-phoros-credentials)
413 (restore-road-network-chart-configuration)
414 (restore-zeb-chart-configuration)
415 (restore-accidents-chart-configuration)
416 (restore-road-section)
417 (update-credentials-dialog)
418 (check-credentials-dialog-statuses)
419 (ignore-errors (apply #'phoros-login *phoros-url* *phoros-credentials*))
420 ;; Kludge: tickle the dialog to make spinbuttons receptive
421 (pipeglade-out "chart_configuration" "set_visible" 1)
422 (pipeglade-out "chart_configuration" "set_visible" 0)
423 (pipeglade-out "chart_road_network" "set_line_cap" 1 "round")
424 (pipeglade-out "chart_road_network" "set_line_join" 1 "round")
425 (pipeglade-out "chart_zeb" "set_line_cap" 1 "round")
426 (pipeglade-out "chart_zeb" "set_line_join" 1 "round")
427 (pipeglade-out "chart_accidents" "set_line_join" 1 "miter")
428 (pipeglade-out "chart_accidents" "set_line_width" 1 1)
429 (pipeglade-out "chart_cursor" "set_source_rgba" 1 *cursor-color*)
430 (pipeglade-out "chart_cursor" "set_line_width" 1 3)
431 (pipeglade-out "chart_cursor" "set_dash" 1 3)
432 (pipeglade-out "chart_cursor" "set_font_size" 1 10)
433 (pipeglade-out "chart_road_network_scale" "set_font_size" 1 10)
434 (pipeglade-out "zeb_network_scale" "set_font_size" 1 10)
435 (pipeglade-out "draw_rearview" "set_source_rgba" 1 *cursor-color*)
436 (pipeglade-out "draw_rearview" "set_line_cap" 1 "round")
437 (pipeglade-out "draw_rearview" "set_line_width" 1 2)
438 (pipeglade-out "draw_rearview" "set_font_size" 1 10)
439 (pipeglade-out "draw_frontview" "set_source_rgba" 1 *cursor-color*)
440 (pipeglade-out "draw_frontview" "set_line_cap" 1 "round")
441 (pipeglade-out "draw_frontview" "set_line_width" 1 2)
442 (pipeglade-out "draw_frontview" "set_font_size" 1 10)
443 (pipeglade-out "version" "set_text" "version" *phoros-version*)
444 (with-open-file (in *pipeglade-in-fifo*
445 :direction :input
446 :if-does-not-exist :error)
447 ;; getting rid of initial feedback from credentials dialog:
448 (with-statusbar-message "please wait" (sleep 1))
449 (clear-input in)
450 (populate-road-section-dialog)
451 (restore-road-section-image-counts)
452 (restore-road-section-selection)
453 (update-road-section-selection)
454 (set-road-section)
455 (update-station (saved-station))
456 (populate-chart-dialog)
457 (refresh-chart)
458 (with-statusbar-message "starting browser"
459 (uiop:run-program (format nil "firefox '~A' &" *phoros-url*)))
460 (bt:make-thread
461 (image-worker :rear-view)
462 :name "rear-view-image-worker")
463 (bt:make-thread
464 (image-worker :front-view)
465 :name "front-view-image-worker")
466 (bt:make-thread
467 #'jump-to-station-worker
468 :name "jump-to-station-worker")
469 (bt:make-thread
470 #'cruise-control-worker
471 :name "cruise-control-worker")
472 (loop
473 for message = (read-line in nil)
475 (cond
476 ((message-name= "quit" message)
477 (pipeglade-out "_" "main_quit")
478 (signal 'attention)
479 (loop-finish))
480 ((and (message-name= "main" message)
481 (string= (message-info message) "closed"))
482 (pipeglade-out "_" "main_quit")
483 (loop-finish))
484 ((message-name= "station_scale" message) ;the sole invocation of jump-to-station
485 (jump-to-station (parse-integer (message-data message) :junk-allowed t)))
486 ((message-name= "show_road_network_chart" message)
487 (pipeglade-out "chart_road_network" "set_visible" (message-info message))
488 (pipeglade-out "chart_road_network_scale" "set_visible" (message-info message)))
489 ((message-name= "show_zeb_chart" message)
490 (pipeglade-out "chart_zeb" "set_visible" (message-info message))
491 (pipeglade-out "chart_zeb_scale" "set_visible" (message-info message)))
492 ((message-name= "show_accidents_chart" message)
493 (pipeglade-out "chart_accidents" "set_visible" (message-info message)))
494 ((message-name= "show_rear_view" message)
495 (setf *show-rear-view-p* (string= (message-info message) "1")))
496 ((message-name= "show_front_view" message)
497 (setf *show-front-view-p* (string= (message-info message) "1")))
498 ((message-name= "big_step" message)
499 (let* ((step (parse-integer (message-data message) :junk-allowed t))
500 (label-text (format nil "~D m" step)))
501 (pipeglade-out "back" "set_label" label-text)
502 (pipeglade-out "forward" "set_label" label-text)
503 (pipeglade-out "big_step_back" "set_label" label-text)
504 (pipeglade-out "big_step_forward" "set_label" label-text)
505 (pipeglade-out "station_scale" "set_increments" 1 step)
506 (setf *big-step* step)))
507 ((message-name= "step_back" message)
508 (stop-cruise-control)
509 (pipeglade-out "station_scale" "set_value" (1- (saved-station))))
510 ((message-name= "step_forward" message)
511 (stop-cruise-control)
512 (pipeglade-out "station_scale" "set_value" (1+ (saved-station))))
513 ((message-name= "big_step_back" message)
514 (stop-cruise-control)
515 (pipeglade-out "station_scale" "set_value" (- (saved-station) *big-step*)))
516 ((message-name= "big_step_forward" message)
517 (stop-cruise-control)
518 (pipeglade-out "station_scale" "set_value" (+ (saved-station) *big-step*)))
519 ((message-name= "back" message)
520 (stop-cruise-control)
521 (cruise-control :backwardp t))
522 ((message-name= "forward" message)
523 (stop-cruise-control)
524 (cruise-control :backwardp nil))
525 ((message-name= "stop" message)
526 (stop-cruise-control))
527 ((message-name= "first_section" message)
528 (set-road-section)
529 (refresh-chart)
530 (pipeglade-out "station_scale" "set_value" 1)
531 (pipeglade-out "station_scale" "set_value" 0))
532 ((message-name= "previous_section" message)
533 (set-road-section :direction :predecessor)
534 (refresh-chart)
535 (pipeglade-out "station_scale" "set_value" 1)
536 (pipeglade-out "station_scale" "set_value" 0))
537 ((message-name= "next_section" message)
538 (set-road-section :direction :successor)
539 (refresh-chart)
540 (pipeglade-out "station_scale" "set_value" 1)
541 (pipeglade-out "station_scale" "set_value" 0))
542 ((message-name= "last_section" message)
543 (set-road-section :direction :last)
544 (refresh-chart)
545 (pipeglade-out "station_scale" "set_value" 1)
546 (pipeglade-out "station_scale" "set_value" 0))
547 ((message-name= "road_sections" message)
548 (collect-road-section-select message))
549 ((message-name= "road_section_ok" message)
550 (digest-road-section-raw-data))
551 ((message-name= "road_section_cncl" message)
552 (restore-road-section-selection)
553 (pipeglade-out "road_section" "set_visible" 0))
554 ((message-name= "road_network" message)
555 (collect-raw-message message *road-network-chart-raw-data*))
556 ((message-name= "zeb" message)
557 (collect-raw-message message *zeb-chart-raw-data*))
558 ((message-name= "render_accidents" message)
559 (setf (first *accidents-chart-raw-data*) (message-info message)))
560 ((message-name= "accidents_from" message)
561 (setf (second *accidents-chart-raw-data*) (message-data message)))
562 ((message-name= "accidents_to" message)
563 (setf (third *accidents-chart-raw-data*) (message-data message)))
564 ((message-name= "chart_configuration_ok" message)
565 (setf *road-network-chart-configuration* (digest-chart-raw-data *road-network-chart-raw-data*))
566 (save-place *road-network-chart-configuration* 'road-network-chart-configuration)
567 (setf *zeb-chart-configuration* (digest-chart-raw-data *zeb-chart-raw-data*))
568 (save-place *zeb-chart-configuration* 'zeb-chart-configuration)
569 (digest-accidents-chart-raw-data)
570 (update-accidents-chart-dialog)
571 (pipeglade-out "text_values" "clear")
572 (refresh-chart))
573 ((message-name= "chart_configuration_cncl" message)
574 (update-accidents-chart-dialog)
575 (setf *accidents-chart-raw-data* (list nil nil nil))
576 (pipeglade-out "chart_configuration" "set_visible" 0))
577 ((message-name= "credentials_check" message)
578 (check-credentials-dialog-statuses))
579 ((message-name= "credentials_ok" message)
580 (check-credentials-dialog-statuses)
581 (when (db-credentials-modifiedp *postgresql-road-network-credentials*)
582 (invalidate-road-section-selection)
583 (invalidate-road-section)
584 (invalidate-road-network-chart-configuration)
585 (populate-road-section-dialog)
586 (update-chart-dialog)
587 (save-road-network-credentials nil))
588 (when (db-credentials-modifiedp *postgresql-zeb-credentials*)
589 (update-chart-dialog)
590 (invalidate-zeb-chart-configuration)
591 (pipeglade-out "text_values" "clear")
592 (refresh-chart)
593 (save-zeb-credentials nil))
594 (when (db-credentials-modifiedp *postgresql-accidents-credentials*)
595 (refresh-chart)
596 (save-accidents-credentials nil))
597 (ignore-errors (apply #'phoros-login *phoros-url* *phoros-credentials*))
598 (forget-images-being-launched)
599 (pipeglade-out "station_scale" "set_value" (saved-station))
600 (update-chart-dialog))
601 ((message-name= "road_network_host" message)
602 (setf (db-credentials-host *postgresql-road-network-credentials*) (message-data message))
603 (save-road-network-credentials t))
604 ((message-name= "road_network_port" message)
605 (setf (db-credentials-port *postgresql-road-network-credentials*)
606 (parse-integer (message-data message) :junk-allowed t))
607 (save-road-network-credentials t))
608 ((message-name= "road_network_ssl" message)
609 (setf (db-credentials-ssl *postgresql-road-network-credentials*) (if (string= (message-data message) "1") :yes :no))
610 (save-road-network-credentials t))
611 ((message-name= "road_network_database" message)
612 (setf (db-credentials-database *postgresql-road-network-credentials*) (message-data message))
613 (save-road-network-credentials t))
614 ((message-name= "road_network_user" message)
615 (setf (db-credentials-user *postgresql-road-network-credentials*) (message-data message))
616 (save-road-network-credentials t))
617 ((message-name= "road_network_password" message)
618 (setf (db-credentials-password *postgresql-road-network-credentials*) (message-data message))
619 (save-road-network-credentials t))
620 ((message-name= "road_network_table" message)
621 (setf (db-credentials-table *postgresql-road-network-credentials*) (message-data message))
622 (save-road-network-credentials t))
623 ((message-name= "zeb_host" message)
624 (setf (db-credentials-host *postgresql-zeb-credentials*) (message-data message))
625 (save-zeb-credentials t))
626 ((message-name= "zeb_port" message)
627 (setf (db-credentials-port *postgresql-zeb-credentials*)
628 (parse-integer (message-data message) :junk-allowed t))
629 (save-zeb-credentials t))
630 ((message-name= "zeb_ssl" message)
631 (setf (db-credentials-ssl *postgresql-zeb-credentials*) (if (string= (message-info message) "1") :yes :no))
632 (save-zeb-credentials t))
633 ((message-name= "zeb_database" message)
634 (setf (db-credentials-database *postgresql-zeb-credentials*) (message-data message))
635 (save-zeb-credentials t))
636 ((message-name= "zeb_user" message)
637 (setf (db-credentials-user *postgresql-zeb-credentials*) (message-data message))
638 (save-zeb-credentials t))
639 ((message-name= "zeb_password" message)
640 (setf (db-credentials-password *postgresql-zeb-credentials*) (message-data message))
641 (save-zeb-credentials t))
642 ((message-name= "zeb_table" message)
643 (setf (db-credentials-table *postgresql-zeb-credentials*) (message-data message))
644 (save-zeb-credentials t))
645 ((message-name= "accidents_host" message)
646 (setf (db-credentials-host *postgresql-accidents-credentials*) (message-data message))
647 (save-accidents-credentials t))
648 ((message-name= "accidents_port" message)
649 (setf (db-credentials-port *postgresql-accidents-credentials*)
650 (parse-integer (message-data message) :junk-allowed t))
651 (save-accidents-credentials t))
652 ((message-name= "accidents_ssl" message)
653 (setf (db-credentials-ssl *postgresql-accidents-credentials*) (if (string= (message-data message) "1") :yes :no))
654 (save-accidents-credentials t))
655 ((message-name= "accidents_database" message)
656 (setf (db-credentials-database *postgresql-accidents-credentials*) (message-data message))
657 (save-accidents-credentials t))
658 ((message-name= "accidents_user" message)
659 (setf (db-credentials-user *postgresql-accidents-credentials*) (message-data message))
660 (save-accidents-credentials t))
661 ((message-name= "accidents_password" message)
662 (setf (db-credentials-password *postgresql-accidents-credentials*) (message-data message))
663 (save-accidents-credentials t))
664 ((message-name= "accidents_table" message)
665 (setf (db-credentials-table *postgresql-accidents-credentials*) (message-data message))
666 (save-accidents-credentials t))
667 ((message-name= "phoros_url" message)
668 (setf *phoros-url* (message-data message))
669 (save-phoros-credentials))
670 ((message-name= "phoros_user" message)
671 (setf (first *phoros-credentials*) (message-data message))
672 (save-phoros-credentials))
673 ((message-name= "phoros_password" message)
674 (setf (second *phoros-credentials*) (message-data message))
675 (save-phoros-credentials))
676 ((message-name= "phoros" message)
677 (run-phoros-browser))
679 (print (list "fallen through:" message)))))))
680 (sb-sys:interactive-interrupt ()
681 (let ((pipeglade-pid
682 (with-open-file (stream *pipeglade-pid-file* :direction :input)
683 (read stream nil))))
684 (uiop:run-program (format nil "kill ~A" pipeglade-pid))))))
686 (defun invalidate-road-section ()
687 (setf *road-section* nil)
688 (save-road-section))
690 (defun invalidate-road-section-selection ()
691 (setf *road-section-selection* '())
692 (save-road-section-selection))
694 (defun invalidate-road-network-chart-configuration ()
695 (setf *road-network-chart-configuration* nil)
696 (save-place *road-network-chart-configuration* 'road-network-chart-configuration))
698 (defun invalidate-zeb-chart-configuration ()
699 (setf *zeb-chart-configuration* nil)
700 (save-place *zeb-chart-configuration* 'zeb-chart-configuration))
702 (defun message-name= (string message)
703 (let ((colon-position (position #\: message)))
704 (string= string (subseq message 0 colon-position))))
706 (defun message-info (message)
707 (let ((colon-position (position #\: message))
708 (space-position (position #\Space message)))
709 (subseq message (1+ colon-position) space-position)))
711 (defun message-data (message)
712 (let ((space-position (position #\Space message)))
713 (when space-position
714 (subseq message (1+ space-position)))))
716 (defun message-data-list (message)
717 (cl-utilities:split-sequence #\Space (message-data message)))
719 (defun collect-road-section-select (message)
720 (let ((data (message-data-list message)))
721 (if (string= (second data) "4") ;"select" column
722 (setf (gethash (parse-integer (first data)) ;row number
723 *road-section-raw-data*)
724 (string= (third data) "1")))))
726 (defun collect-accidents-message-data (&key (renderp 0 renderp-p) (from nil from-p) (to nil to-p) (ok-pressed nil ok-pressed-p))
727 (when renderp-p (setf (first *accidents-chart-raw-data*) renderp))
728 (when from-p (setf (second *accidents-chart-raw-data*) (parse-integer from :junk-allowed t)))
729 (when to-p (setf (third *accidents-chart-raw-data*) (parse-integer to :junk-allowed t))))
731 (defun collect-raw-message (message place)
732 (unless (string= (message-info message) "clicked")
733 (let ((data (message-data-list message)))
734 (setf (gethash (list (parse-integer (first data)) ;row number
735 (parse-integer (second data))) ;column number
736 place)
737 (third data)))))
739 (defun digest-road-section-raw-data ()
740 (when *postgresql-road-network-credentials*
741 (let ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))))
742 (maphash (lambda (key value)
743 (if value
744 (pushnew key *road-section-selection*)
745 (setf *road-section-selection* (remove key *road-section-selection*))))
746 *road-section-raw-data*)
747 (setf *road-section-selection* (sort *road-section-selection* #'<))
748 (save-road-section-selection)
749 (set-road-section)
750 (save-road-section)
752 (when (update-station) ;new section
753 (restore-road-section-image-counts)
754 (prepare-chart))
755 (clrhash *road-section-raw-data*))))
757 (defstruct (data-style (:type list)) chartp drawablep textp name color width dash)
759 (defun clear-main-window ()
760 (dolist (drawingarea '("chart_accidents" "chart_road_network" "chart_zeb" "chart_cursor" "chart_road_network_scale" "chart_zeb_scale" "draw_rearview" "draw_frontview"))
761 (pipeglade-out drawingarea "remove" 2))
762 (dolist (image '("img_rearview" "img_frontview"))
763 (pipeglade-out image "set_from_file"))
764 (pipeglade-out "text_values" "clear"))
766 (defun digest-chart-raw-data (raw-data)
767 "Return the information read from raw-data in chart configuration format."
768 (let* ((row-count
769 (loop
770 for (row column) being each hash-key of raw-data
771 when (zerop column) ;arbitrary column representing its row
772 count it))
773 (chart-configuration
774 (make-array (list row-count))))
775 (loop
776 for i from 0 below row-count
778 (setf (svref chart-configuration i)
779 (make-data-style)))
780 (loop
781 for (row column) being each hash-key of raw-data using (hash-value value)
783 (case column
784 (0 ;column name
785 (setf (data-style-name (svref chart-configuration row)) value))
786 ;; 1 would be type
787 (2 ;width
788 (setf (data-style-width (svref chart-configuration row)) value))
789 (3 ;color
790 (setf (data-style-color (svref chart-configuration row)) value))
791 (4 ;dash
792 (setf (data-style-dash (svref chart-configuration row)) value))
793 (5 ;selected
794 (setf (data-style-chartp (svref chart-configuration row)) (string= value "1")))
796 (setf (data-style-textp (svref chart-configuration row)) (string= value "1")))
798 (setf (data-style-drawablep (svref chart-configuration row)) (string= value "1")))))
799 chart-configuration))
801 (defun digest-accidents-chart-raw-data ()
802 (setf *accidents-chart-configuration*
803 (mapcar (lambda (configuration-value raw-value)
804 (or (format nil "~D" (parse-integer raw-value :junk-allowed t)) configuration-value))
805 *accidents-chart-configuration*
806 *accidents-chart-raw-data*))
807 (save-accidents-chart-configuration))
809 (defun road-network-chart-data (column vnk nnk chart-height)
810 "Return a list of lists of station and column values between vnk
811 and nnk scaled into chart-height; the minimum column value; and the
812 maximum column value. Both minimum and maximum are nil if data is
813 constant."
814 (let ((table (intern (db-credentials-table *postgresql-road-network-credentials*))))
815 (with-connection *postgresql-road-network-credentials*
816 (setf column (intern (string-upcase column)))
817 (destructuring-bind (minimum maximum)
818 (query (:select (:type (:min column) real)
819 (:type (:max column) real)
820 :from table
821 :where (:and (:= 'vnk vnk)
822 (:= 'nnk nnk)))
823 :list)
824 (if (and (numberp minimum) (numberp maximum))
825 (let* ((span (- maximum minimum))
826 (m (if (zerop span)
828 (/ chart-height span)))
829 (b (if (zerop span)
830 (* chart-height 1/2)
831 (+ chart-height (* m minimum)))))
832 (values
833 (query (:order-by
834 (:select 'nk-station
835 (:- b (:* m (:type column real)))
836 :from table
837 :where (:and (:= 'vnk vnk)
838 (:= 'nnk nnk)))
839 'nk-station))
840 ;; (unless (zerop span) minimum)
841 ;; (unless (zerop span) maximum)
842 minimum
843 maximum
845 (values nil nil nil))))))
847 (defun zeb-chart-data (column vnk nnk chart-height)
848 "Return a list of lists of station and column values between vnk
849 and nnk scaled into chart-height; the minimum column value; and the
850 maximum column value. Both minimum and maximum are nil if data is
851 constant."
852 (let ((table (intern (db-credentials-table *postgresql-zeb-credentials*))))
853 (with-connection *postgresql-zeb-credentials*
854 (setf column (intern (string-upcase column)))
855 (destructuring-bind (minimum maximum)
856 (query (:select (:type (:min column) real)
857 (:type (:max column) real)
858 :from table
859 :where (:and (:= 'vnk vnk)
860 (:= 'nnk nnk)))
861 :list)
862 (if (and (numberp minimum) (numberp maximum))
863 (let* ((span (- maximum minimum))
864 (m (if (zerop span)
866 (/ chart-height span)))
867 (b (if (zerop span)
868 (* chart-height 1/2)
869 (+ chart-height (* m minimum)))))
870 (values
871 (query (:order-by
872 (:select 'vst
873 (:- b (:* m (:type column real)))
874 'bst
875 (:- b (:* m (:type column real)))
876 :from table
877 :where (:and (:= 'vnk vnk)
878 (:= 'nnk nnk)))
879 'vst))
880 ;; (unless (zerop span) minimum)
881 ;; (unless (zerop span) maximum)
882 minimum
883 maximum
885 (values nil nil nil))))))
889 (defun road-network-text-value (column vnk nnk station)
890 "Return column value at station between vnk and nnk."
891 (let ((table (intern (db-credentials-table *postgresql-road-network-credentials*))))
892 (with-connection *postgresql-road-network-credentials*
893 (setf column (intern (string-upcase column)))
894 (ignore-errors
895 (query (:select column
896 :from table
897 :where (:and (:= 'vnk vnk)
898 (:= 'nnk nnk)
899 (:= 'nk_station station)))
900 :single)))))
902 (defun zeb-text-value (column vnk nnk station)
903 "Return column value at station between vnk and nnk."
904 (let ((table (intern (db-credentials-table *postgresql-zeb-credentials*))))
905 (with-connection *postgresql-zeb-credentials*
906 (setf column (intern (string-upcase column)))
907 (ignore-errors
908 (query (:select column
909 :from table
910 :where (:and (:= 'vnk vnk)
911 (:= 'nnk nnk)
912 (:between station 'vst 'bst)))
913 :single)))))
915 (defun show-text (row-number station text-data-function column vnk nnk color width dash)
916 (let ((value (funcall text-data-function column vnk nnk station)))
917 (pipeglade-out "text_values" "set" row-number 0 column)
918 (pipeglade-out "text_values" "set" row-number 1 value)
919 (pipeglade-out "text_values" "set" row-number 2 color)
920 (pipeglade-out "text_values" "set" row-number 3 (* 4 (parse-integer width :junk-allowed t))))) ;text size
922 (defun put-text-values (vnk nnk station)
923 (let ((row-number 0))
924 (when (vectorp *road-network-chart-configuration*)
925 (loop
926 for style-definition across *road-network-chart-configuration*
928 (when (data-style-textp style-definition)
929 (show-text row-number station #'road-network-text-value (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
930 (incf row-number))))
931 (when (vectorp *zeb-chart-configuration*)
932 (loop
933 for style-definition across *zeb-chart-configuration*
935 (when (data-style-textp style-definition)
936 (show-text row-number station #'zeb-text-value (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
937 (incf row-number))))))
939 (defun accidents-data (vnk nnk &key
940 (year-min most-negative-fixnum)
941 (year-max most-positive-fixnum))
942 "Return a list of plists containing accident data for the road
943 section between vnk and nnk."
944 (let ((table (intern (db-credentials-table *postgresql-accidents-credentials*))))
945 (with-connection *postgresql-accidents-credentials*
946 (query (:order-by
947 (:select 'nk-station 'fahrtrichtung 'unfalltyp 'unfallkategorie 'alkohol
948 :from table
949 :where (:and (:= 'vnk vnk)
950 (:= 'nnk nnk)
951 (:between 'jahr year-min year-max)))
952 'nk-station 'jahr 'monat 'tag 'stunde 'minuten)
953 :plists))))
955 (defun populate-road-section-dialog ()
956 (with-statusbar-message "populating road section list"
957 (with-spinner "road_section_spinner"
958 (pipeglade-out "road_sections" "clear")
959 (ignore-errors
960 (with-connection *postgresql-road-network-credentials*
961 (let ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))))
962 (loop
963 for (vnk nnk length) in sections
964 for row-number from 0
966 (add-vnk-nnk-leaf vnk nnk length row-number))))))))
968 (defun update-credentials-dialog ()
969 (with-statusbar-message "initialising credentials"
970 (pipeglade-out "road_network_host" "set_text" (db-credentials-host *postgresql-road-network-credentials*))
971 (pipeglade-out "road_network_port" "set_text" (db-credentials-port *postgresql-road-network-credentials*))
972 (pipeglade-out "road_network_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-road-network-credentials*) :no) 0 1))
973 (pipeglade-out "road_network_database" "set_text" (db-credentials-database *postgresql-road-network-credentials*))
974 (pipeglade-out "road_network_table" "set_text" (db-credentials-table *postgresql-road-network-credentials*))
975 (pipeglade-out "road_network_user" "set_text" (db-credentials-user *postgresql-road-network-credentials*))
976 (pipeglade-out "road_network_password" "set_text" (db-credentials-password *postgresql-road-network-credentials*))
977 (pipeglade-out "road_network_status" "set_text" "?")
978 (pipeglade-out "zeb_host" "set_text" (db-credentials-host *postgresql-zeb-credentials*))
979 (pipeglade-out "zeb_port" "set_text" (db-credentials-port *postgresql-zeb-credentials*))
980 (pipeglade-out "zeb_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-zeb-credentials*) :no) 0 1))
981 (pipeglade-out "zeb_database" "set_text" (db-credentials-database *postgresql-zeb-credentials*))
982 (pipeglade-out "zeb_table" "set_text" (db-credentials-table *postgresql-zeb-credentials*))
983 (pipeglade-out "zeb_user" "set_text" (db-credentials-user *postgresql-zeb-credentials*))
984 (pipeglade-out "zeb_password" "set_text" (db-credentials-password *postgresql-zeb-credentials*))
985 (pipeglade-out "zeb_status" "set_text" "?")
986 (pipeglade-out "accidents_host" "set_text" (db-credentials-host *postgresql-accidents-credentials*))
987 (pipeglade-out "accidents_port" "set_text" (db-credentials-port *postgresql-accidents-credentials*))
988 (pipeglade-out "accidents_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-accidents-credentials*) :no) 0 1))
989 (pipeglade-out "accidents_database" "set_text" (db-credentials-database *postgresql-accidents-credentials*))
990 (pipeglade-out "accidents_table" "set_text" (db-credentials-table *postgresql-accidents-credentials*))
991 (pipeglade-out "accidents_user" "set_text" (db-credentials-user *postgresql-accidents-credentials*))
992 (pipeglade-out "accidents_password" "set_text" (db-credentials-password *postgresql-accidents-credentials*))
993 (pipeglade-out "accidents_status" "set_text" "?")
994 (when *phoros-credentials*
995 (destructuring-bind (user password) *phoros-credentials*
996 (pipeglade-out "phoros_url" "set_text" *phoros-url*)
997 (pipeglade-out "phoros_user" "set_text" user)
998 (pipeglade-out "phoros_password" "set_text" password)
999 (pipeglade-out "phoros_status" "set_text" "?")))))
1001 (defun check-credentials-dialog-statuses ()
1002 (with-statusbar-message "checking road network db connection"
1003 (pipeglade-out "road_network_status" "set_text" (check-db *postgresql-road-network-credentials*)))
1004 (with-statusbar-message "checking zeb db connection"
1005 (pipeglade-out "zeb_status" "set_text" (check-db *postgresql-zeb-credentials*)))
1006 (with-statusbar-message "checking accidents db connection"
1007 (pipeglade-out "accidents_status" "set_text" (check-db *postgresql-accidents-credentials*)))
1008 (with-statusbar-message "checking Phoros connection"
1009 (pipeglade-out "phoros_status" "set_text" (and *phoros-url*
1010 *phoros-credentials*
1011 (apply #'check-phoros *phoros-url* *phoros-credentials*)))))
1013 (defun save-place (place filename-stump)
1014 "Save place into a file whose name is based on symbol filename-stump."
1015 (let ((cache-file-name (cache-file-name filename-stump)))
1016 (ensure-directories-exist cache-file-name)
1017 (with-open-file (stream cache-file-name
1018 :direction :output
1019 :if-exists :supersede)
1020 (prin1 place stream))))
1022 (defmacro restore-place (place filename-stump &optional default)
1023 "Restore place from a file whose name is based on symbol filename-stump."
1024 (cl-utilities:with-unique-names (stream)
1025 `(with-open-file (stream (cache-file-name ,filename-stump)
1026 :direction :input
1027 :if-does-not-exist nil)
1028 (if stream
1029 (setf ,place (read stream))
1030 (setf ,place ,default)))))
1033 (defun save-road-section-selection ()
1034 "Save the list of road sections selected for processing."
1035 (save-place *road-section-selection* 'road-section-selection))
1037 (defun restore-road-section-selection ()
1038 (restore-place *road-section-selection* 'road-section-selection))
1040 (defun update-road-section-selection ()
1041 (with-statusbar-message "restoring road section selection"
1042 (with-spinner "road_section_spinner"
1043 (ignore-errors
1044 (with-connection *postgresql-road-network-credentials*
1045 (let ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))))
1046 (loop
1047 for row-number from 0 below (length sections)
1049 (if (find row-number *road-section-selection*)
1050 (pipeglade-out "road_sections" "set" row-number 4 1)
1051 (pipeglade-out "road_sections" "set" row-number 4 0))))))
1052 (pipeglade-out "road_sections" "scroll"
1053 (or (ignore-errors (apply #'min *road-section-selection*))
1055 0))))
1057 (defun restore-road-section-image-counts ()
1058 (with-statusbar-message "restoring road section image counts"
1059 (ignore-errors
1060 (with-connection *postgresql-road-network-credentials*
1061 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1062 (sections (sections table)))
1063 (loop
1064 for (vnk nnk) in sections
1065 for row-number from 0
1066 do (multiple-value-bind (rearview-image-data rearview-cached-p)
1067 (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 t :from-cache-only t)
1068 (multiple-value-bind (frontview-image-data frontview-cached-p)
1069 (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 nil :from-cache-only t)
1070 (when (and rearview-cached-p frontview-cached-p)
1071 (pipeglade-out "road_sections" "set" row-number 3 (+ (length rearview-image-data) (length frontview-image-data))))))))))))
1073 (defun save-road-network-credentials (modifiedp)
1074 (setf (db-credentials-modifiedp *postgresql-road-network-credentials*) modifiedp)
1075 (save-place *postgresql-road-network-credentials* 'road-network-credentials))
1077 (defun restore-road-network-credentials ()
1078 (restore-place *postgresql-road-network-credentials* 'road-network-credentials *postgresql-road-network-credentials*))
1080 (defun save-zeb-credentials (modifiedp)
1081 (setf (db-credentials-modifiedp *postgresql-zeb-credentials*) modifiedp)
1082 (save-place *postgresql-zeb-credentials* 'zeb-credentials))
1084 (defun restore-zeb-credentials ()
1085 (restore-place *postgresql-zeb-credentials* 'zeb-credentials *postgresql-zeb-credentials*))
1087 (defun save-accidents-credentials (modifiedp)
1088 (setf (db-credentials-modifiedp *postgresql-accidents-credentials*) modifiedp)
1089 (save-place *postgresql-accidents-credentials* 'accidents-credentials))
1091 (defun restore-accidents-credentials ()
1092 (restore-place *postgresql-accidents-credentials* 'accidents-credentials *postgresql-accidents-credentials*))
1094 (defun save-phoros-credentials ()
1095 (save-place *phoros-credentials* 'phoros-credentials)
1096 (save-place *phoros-url* 'phoros-url))
1098 (defun restore-phoros-credentials ()
1099 (restore-place *phoros-credentials* 'phoros-credentials *phoros-credentials*)
1100 (restore-place *phoros-url* 'phoros-url *phoros-url*))
1102 (defun save-road-section ()
1103 "Save road-section into cache directory."
1104 (save-place *road-section* 'road-section))
1106 (defun restore-road-section ()
1107 (restore-place *road-section* 'road-section))
1109 (defun save-accidents-chart-configuration ()
1110 (save-place *accidents-chart-configuration* 'accidents-chart-configuration))
1112 (defun save-station (station)
1113 "Save position of chart cursor into cache directory."
1114 (setf *station* station)
1115 (save-place station 'station))
1117 (defun saved-station ()
1118 (let ((cache-file-name (cache-file-name 'station))
1119 station)
1120 (ensure-directories-exist cache-file-name)
1121 (with-open-file (stream cache-file-name
1122 :direction :input
1123 :if-does-not-exist nil)
1124 (when stream (setf station (read stream)))
1125 (or station 0))))
1127 (defun restore-road-network-chart-configuration ()
1128 (unless (db-credentials-modifiedp *postgresql-road-network-credentials*)
1129 (restore-place *road-network-chart-configuration* 'road-network-chart-configuration)))
1131 (defun restore-zeb-chart-configuration ()
1132 (unless (db-credentials-modifiedp *postgresql-zeb-credentials*)
1133 (restore-place *zeb-chart-configuration* 'zeb-chart-configuration)))
1135 (defun restore-accidents-chart-configuration ()
1136 (unless (db-credentials-modifiedp *postgresql-accidents-credentials*)
1137 (restore-place *accidents-chart-configuration* 'accidents-chart-configuration (list "1" "1999" "2030"))))
1139 (defun set-road-section (&key direction)
1140 (ignore-errors
1141 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1142 (sections (sections table))
1143 (sections-current (position (cdr *road-section*) sections :test #'equal))
1144 (selection-current (position sections-current *road-section-selection*)))
1145 (cond ((and *road-section-selection* (eq direction :predecessor))
1146 (let ((selection-predecessor (ignore-errors (nth (1- selection-current) *road-section-selection*))))
1147 (when selection-predecessor
1148 (setf *road-section*
1149 (cons table (nth selection-predecessor sections))))))
1150 ((and *road-section-selection* (eq direction :successor))
1151 (let* ((selection-successor (nth (1+ selection-current) *road-section-selection*)))
1152 (when selection-successor
1153 (setf *road-section*
1154 (cons table (nth selection-successor sections))))))
1155 ((and *road-section-selection* (eq direction :last))
1156 (setf *road-section* (cons table
1157 (nth (car (last *road-section-selection*)) sections))))
1158 (*road-section-selection*
1159 (setf *road-section* (cons table
1160 (nth (first *road-section-selection*) sections))))
1162 (setf *road-section* nil))))))
1164 (let (old-road-section)
1165 (defun update-station (&optional station)
1166 "If either station is supplied or *road-section* has changed, change station widget in UI. Return nil if nothing has been changed"
1167 (cond ((numberp station)
1168 (setf old-road-section *road-section*)
1169 (pipeglade-out "station_scale" "set_value" station)
1170 (save-station station))
1171 ((not (and old-road-section
1172 (equal (cdr *road-section*) (cdr old-road-section))
1173 (equal (string (car *road-section*)) (string (car *road-section*))))) ;comparing uninterned symbols
1174 (setf old-road-section *road-section*)
1175 (pipeglade-out "station_scale" "set_value" 0)
1176 (save-station 0))
1178 nil))))
1180 (defun check-db (db-credentials &aux result)
1181 "Check database connection and presence of table or view table-name.
1182 Return a string describing the outcome."
1183 (let ((table-name (db-credentials-table db-credentials)))
1184 (unless
1185 (ignore-errors
1186 (with-connection db-credentials
1187 (if (or (table-exists-p table-name)
1188 (view-exists-p table-name))
1189 (setf result "ok")
1190 (setf result "table or view missing"))))
1191 (setf result "connection failure"))
1192 result))
1194 (defun check-phoros (url user-name password)
1195 "Check connection to phoros server. Return a string describing the
1196 outcome."
1197 (let ((*phoros-url* url)
1198 (*phoros-cookies* nil))
1199 (unwind-protect
1200 (handler-case (phoros-login url user-name password)
1201 (usocket:ns-host-not-found-error () "host not found")
1202 (usocket:connection-refused-error () "connection refused")
1203 (error (c) (format nil "~A" c))
1204 (:no-error (result) (if result "ok" "wrong user or password")))
1205 (ignore-errors (phoros-logout)))))
1207 (defun populate-chart-dialog ()
1208 (with-statusbar-message "initialising chart configuration"
1209 (update-chart-dialog-treeview "road_network" *postgresql-road-network-credentials* *road-network-chart-configuration*)
1210 (update-chart-dialog-treeview "zeb" *postgresql-zeb-credentials* *zeb-chart-configuration*)
1211 (update-accidents-chart-dialog)))
1214 (defun update-chart-dialog ()
1215 (with-statusbar-message "updating chart configuration"
1216 (when (db-credentials-modifiedp *postgresql-road-network-credentials*)
1217 (update-chart-dialog-treeview "road_network" *postgresql-road-network-credentials* *road-network-chart-configuration*)
1218 (save-road-network-credentials nil))
1219 (when (db-credentials-modifiedp *postgresql-zeb-credentials*)
1220 (update-chart-dialog-treeview "zeb" *postgresql-zeb-credentials* *zeb-chart-configuration*)
1221 (save-zeb-credentials nil))
1222 (when (db-credentials-modifiedp *postgresql-accidents-credentials*)
1223 (update-accidents-chart-dialog)
1224 (save-accidents-credentials nil))))
1226 (defun update-chart-dialog-treeview (treeview db-credentials chart-configuration)
1227 (with-statusbar-message "updating chart configuration"
1228 (ignore-errors
1229 (with-connection db-credentials
1230 (present-db-columns (table-description (db-credentials-table db-credentials)) treeview chart-configuration)))))
1233 (defun update-accidents-chart-dialog ()
1234 (pipeglade-out "render_accidents" "set_active" (first *accidents-chart-configuration*))
1235 (pipeglade-out "accidents_from" "set_text" (second *accidents-chart-configuration*))
1236 (pipeglade-out "accidents_to" "set_text" (third *accidents-chart-configuration*)))
1238 (defun present-db-columns (columns treeview chart-configuration)
1239 (pipeglade-out treeview "clear")
1240 (loop
1241 for (column-name type) in (sort columns #'string-lessp :key #'car)
1242 for row-number from 0
1244 (let ((selected-column (find column-name chart-configuration :key #'data-style-name :test #'string-equal))
1245 (drawablep (numeric-type-p type)))
1246 (pipeglade-out treeview "set" row-number 0 column-name)
1247 (pipeglade-out treeview "set" row-number 1 type)
1248 (pipeglade-out treeview "set" row-number 2 (or (data-style-width selected-column) 2))
1249 (pipeglade-out treeview "set" row-number 3 (or (data-style-color selected-column) "black"))
1250 (pipeglade-out treeview "set" row-number 4 (or (data-style-dash selected-column) ""))
1251 (pipeglade-out treeview "set" row-number 5 (if (and drawablep (data-style-chartp selected-column)) 1 0))
1252 (pipeglade-out treeview "set" row-number 6 (if (data-style-textp selected-column) 1 0))
1253 (pipeglade-out treeview "set" row-number 7 (if drawablep 1 0))
1254 (pipeglade-out treeview "set_cursor" row-number))) ;tickle initial pipeglade output
1255 (pipeglade-out treeview "set_cursor")
1256 (pipeglade-out treeview "scroll" 0 0))
1258 (defun numeric-type-p (type)
1259 (some #'identity (mapcar (lambda (x) (search x type))
1260 '("float" "double" "int" "numeric" "serial"))))
1262 (defun add-vnk-nnk-leaf (vnk nnk length row-number)
1263 "Put a leaf into road-sections tree."
1264 (pipeglade-out "road_sections" "set" row-number 0 vnk)
1265 (pipeglade-out "road_sections" "set" row-number 1 nnk)
1266 (pipeglade-out "road_sections" "set" row-number 2 length))
1268 (defun prepare-chart ()
1269 "Prepare chart for the road section between vnk and nnk in table in
1270 current database."
1271 (when *road-section*
1272 (destructuring-bind (table vnk nnk road-section-length) *road-section*
1273 (pipeglade-out "ovl_chart" "set_size_request" (+ *chart-tail* road-section-length) (+ *chart-height* *chart-fringe*))
1274 (pipeglade-out "vnk" "set_text" vnk)
1275 (pipeglade-out "nnk" "set_text" nnk)
1276 (pipeglade-out "length" "set_text" road-section-length)
1277 (draw-chart-cursor-scale road-section-length)
1278 (pipeglade-out "station_scale" "set_range" 0 road-section-length)
1279 (setf *road-section* (list table vnk nnk road-section-length))
1280 (save-road-section)
1281 (draw-graphs vnk nnk)
1282 (pipeglade-out "station_scale" "set_value" (saved-station)))))
1284 (defun place-chart-cursor (station)
1285 "Move chart cursor to station."
1286 (when station
1287 (pipeglade-out "chart_cursor" "remove" 2)
1288 (pipeglade-out "chart_cursor" "move_to" 2 station 0)
1289 (pipeglade-out "chart_cursor" "line_to" 2 station (+ *chart-height* *chart-fringe*))
1290 (pipeglade-out "chart_cursor" "stroke" 2)
1291 (pipeglade-out "chart_scroll" "hscroll_to_range" (- station 200) (+ station 200))
1292 (pipeglade-out "chart_road_network_scale" "translate" "=3" station 0)
1293 (pipeglade-out "chart_zeb_scale" "translate" "=3" station 0)))
1295 (defun refresh-chart ()
1296 "Redraw chart."
1297 (when (= (length *road-section*) 4)
1298 (prepare-chart)))
1300 (defun draw-graphs (vnk nnk)
1301 "Draw graphs for the columns in *zeb-chart-configuration* and
1302 *road-network-chart-configuration*. Delete existing graphs first."
1303 (with-statusbar-message "drawing chart"
1304 (with-spinner "chart_spinner"
1305 (pipeglade-out "chart_road_network" "remove" 2)
1306 (pipeglade-out "chart_road_network_scale" "remove" 2)
1307 (pipeglade-out "chart_road_network_scale" "translate" "=3" 0 0)
1308 (pipeglade-out "chart_zeb" "remove" 2)
1309 (pipeglade-out "chart_zeb_scale" "remove" 2)
1310 (pipeglade-out "chart_zeb_scale" "translate" "=3" 0 0)
1311 (let ((scale-position *scale-distance*))
1312 (with-statusbar-message "drawing road-network chart"
1313 (when (vectorp *road-network-chart-configuration*)
1314 (loop
1315 for style-definition across *road-network-chart-configuration*
1317 (when (data-style-chartp style-definition)
1318 (ignore-errors
1319 (draw-graph #'road-network-chart-data "chart_road_network" (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
1320 (draw-scale scale-position #'road-network-chart-data "chart_road_network_scale" (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
1321 (incf scale-position *scale-distance*))))))
1322 (with-statusbar-message "drawing zeb chart"
1323 (when (vectorp *zeb-chart-configuration*)
1324 (loop
1325 for style-definition across *zeb-chart-configuration*
1327 (when (data-style-chartp style-definition)
1328 (ignore-errors
1329 (draw-graph #'zeb-chart-data "chart_zeb" (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
1330 (draw-scale scale-position #'zeb-chart-data "chart_zeb_scale" (data-style-name style-definition) vnk nnk (data-style-color style-definition) (data-style-width style-definition) (data-style-dash style-definition))
1331 (incf scale-position *scale-distance*)))))))
1332 (pipeglade-out "chart_accidents" "remove" 2)
1333 (ignore-errors
1334 (draw-accidents vnk nnk)))))
1336 (defun draw-graph (chart-data-function chart column vnk nnk color width dash)
1337 (multiple-value-bind (line minimum maximum)
1338 (funcall chart-data-function column vnk nnk *chart-height*)
1339 (let ((line-fragments
1340 (cl-utilities:split-sequence-if #'(lambda (x)
1341 (eq (second x) :null))
1342 line
1343 :remove-empty-subseqs t)))
1344 (pipeglade-out chart "set_source_rgba" 2 color)
1345 (pipeglade-out chart "set_line_width" 2 width)
1346 (pipeglade-out chart "set_dash" 2 dash)
1347 (dolist (line-fragment line-fragments)
1348 (pipeglade-out chart "move_to" 2 (first (car line-fragment)) (second (car line-fragment)))
1349 (dolist (line-vertex (cdr line-fragment))
1350 (pipeglade-out chart "line_to" 2 (first line-vertex) (second line-vertex)))
1351 (pipeglade-out chart "stroke" 2)))))
1353 (defun draw-scale (position chart-data-function chart column vnk nnk color width dash)
1354 (multiple-value-bind (line minimum maximum)
1355 (funcall chart-data-function column vnk nnk *chart-height*)
1356 (pipeglade-out chart "set_source_rgba" 2 color)
1357 (pipeglade-out chart "set_line_width" 2 width)
1358 (pipeglade-out chart "move_to" 2 position 0)
1359 (pipeglade-out chart "line_to" 2 position *chart-height*)
1360 (dolist (tick (axis-ticks minimum maximum 5 *chart-height* t))
1361 (pipeglade-out chart "move_to" 2 position (format nil "~F" (second tick)))
1362 (pipeglade-out chart "rel_line_to" 2 (* 2 (parse-integer width)) 0)
1363 (pipeglade-out chart "move_to" 2 position (format nil "~F" (second tick)))
1364 (pipeglade-out chart "rel_move_to" 2 (- (parse-integer width)) 0)
1365 (pipeglade-out chart "rel_move_for" 2 "e" (first tick))
1366 (pipeglade-out chart "show_text" 2 (first tick)))
1367 (pipeglade-out chart "move_to" 2 position (format nil "~F" (+ *chart-height* (/ *chart-fringe* 2))))
1368 (pipeglade-out chart "rel_move_for" 2 "c" column)
1369 (pipeglade-out chart "show_text" 2 column)
1370 (pipeglade-out chart "stroke" 2)))
1372 (defun draw-chart-cursor-scale (length)
1373 (let ((y-position (+ *chart-height* *chart-fringe*))
1374 (number-of-ticks (round length 100)))
1375 (pipeglade-out "chart_cursor" "remove" 4)
1376 (dolist (tick (axis-ticks 0 length number-of-ticks length nil))
1377 (pipeglade-out "chart_cursor" "move_to" 4 (second tick) y-position)
1378 (pipeglade-out "chart_cursor" "line_to" 4 (second tick) (- y-position 3))
1379 (pipeglade-out "chart_cursor" "rel_move_for" 4 "s" (first tick))
1380 (pipeglade-out "chart_cursor" "show_text" 4 (first tick)))
1381 (pipeglade-out "chart_cursor" "stroke" 4)))
1383 (defun axis-ticks (minimum maximum n chart-size reversep)
1384 (let ((range (- maximum minimum)))
1385 (if (zerop range)
1386 (list (list (format nil "~F" minimum) (/ chart-size 2)))
1387 (let* ((a (if reversep
1388 (- (/ chart-size range))
1389 (/ chart-size range)))
1390 (b (if reversep
1391 (* a maximum)
1392 (- (* a maximum) chart-size)))
1393 (min-step (/ range (1+ n)))
1394 (max-step (/ range (1- n)))
1395 (max-exp (log max-step 10))
1396 (int-exp (floor max-exp))
1397 (norm-min (floor (/ min-step (expt 10 int-exp))))
1398 (norm-max (floor (/ max-step (expt 10 int-exp))))
1399 (norm (cond
1400 ((or (= norm-min 1) (= norm-max 1))
1402 ((or (<= norm-min 4 norm-max) (<= norm-min 5 norm-max) (<= norm-min 6 norm-max))
1404 ((or (<= norm-min 2) (<= norm-min 3 norm-max))
1405 2.5)
1406 ((<= 7 norm-max)
1409 norm-max))) ;can't happen
1410 (step (* norm (expt 10 int-exp)))
1411 (start (- minimum (nth-value 1 (fceiling minimum step)))))
1412 (loop
1413 for i from start to maximum by step
1414 collect (list (if (minusp int-exp)
1415 (format nil "~,VF" (- int-exp) i)
1416 (format nil "~A" (round i)))
1417 (- (* a i) b)))))))
1419 (defun draw-accidents (vnk nnk)
1420 (when (string-equal (first *accidents-chart-configuration*) "1")
1421 (let* ((year-min (second *accidents-chart-configuration*))
1422 (year-max (third *accidents-chart-configuration*))
1423 (accidents (accidents-data vnk nnk :year-min year-min :year-max year-max))
1424 (current-station -1)
1425 (zeroth-position -1)
1426 y1-position
1427 y2-position)
1428 (dolist (accident accidents)
1429 (unless (= current-station (getf accident :nk-station))
1430 (setf y1-position (- *chart-height* zeroth-position))
1431 (setf y2-position zeroth-position)
1432 (setf y0-position (+ (/ *chart-height* 2) zeroth-position)))
1433 (setf current-station (getf accident :nk-station))
1434 (cond ((= 1 (getf accident :fahrtrichtung))
1435 (draw-accident accident (decf y1-position 10)))
1436 ((= 2 (getf accident :fahrtrichtung))
1437 (draw-accident accident (incf y2-position 10)))
1439 (draw-accident accident (incf y0-position 10))))))))
1441 (defun draw-accident (accident y-position)
1442 "Put graphical representation of accident on chart."
1443 (destructuring-bind (&key nk-station fahrtrichtung unfalltyp unfallkategorie alkohol)
1444 accident
1445 (when (and (numberp alkohol) (plusp alkohol)) (draw-triangle nk-station y-position "lightblue"))
1446 (case unfallkategorie
1447 (1 (draw-rectangle nk-station y-position 10 "black")
1448 (draw-circle nk-station y-position 8 (accident-type-color unfalltyp)))
1449 (2 (draw-circle nk-station y-position 8 (accident-type-color unfalltyp)))
1450 (3 (draw-circle nk-station y-position 6 (accident-type-color unfalltyp)))
1451 (4 (draw-circle nk-station y-position 6 "white")
1452 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1453 (5 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1454 (6 (draw-triangle nk-station y-position "lightblue")
1455 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1456 (t (draw-circle nk-station y-position 4 (accident-type-color unfalltyp))))))
1458 (defun draw-circle (x y diameter color)
1459 (pipeglade-out "chart_accidents" "set_source_rgba" 2 "black")
1460 (pipeglade-out "chart_accidents" "arc" 2 x y (/ diameter 2) 0 360)
1461 (pipeglade-out "chart_accidents" "stroke_preserve" 2)
1462 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1463 (pipeglade-out "chart_accidents" "fill" 2))
1465 (defun draw-rectangle (x y diameter color)
1466 (let ((radius (/ diameter 2)))
1467 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1468 (pipeglade-out "chart_accidents" "rectangle" 2 (- x radius) (- y radius) diameter diameter)
1469 (pipeglade-out "chart_accidents" "fill" 2)))
1471 (defun draw-triangle (x y color)
1472 (pipeglade-out "chart_accidents" "set_source_rgba" 2 "black")
1473 (pipeglade-out "chart_accidents" "move_to" 2 (- x 3) (- y 6))
1474 (pipeglade-out "chart_accidents" "line_to" 2 (+ x 3) (- y 6))
1475 (pipeglade-out "chart_accidents" "line_to" 2 x (+ y 9))
1476 (pipeglade-out "chart_accidents" "close_path" 2)
1477 (pipeglade-out "chart_accidents" "stroke_preserve" 2)
1478 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1479 (pipeglade-out "chart_accidents" "fill" 2))
1481 (defun accident-type-color (accident-type)
1482 (case accident-type
1483 (1 "green")
1484 (2 "yellow")
1485 (3 "red")
1486 (4 "white")
1487 (5 "lightblue")
1488 (6 "orange")
1489 (7 "black")
1490 (t "darkblue")))
1492 (defun iso-time (time)
1493 (when time
1494 (multiple-value-bind (seconds deciseconds)
1495 (floor time)
1496 (multiple-value-bind (second minute hour date month year day daylight-p zone)
1497 (decode-universal-time seconds)
1498 (format nil "~D-~2,'0D-~2,'0D\\n~2,'0D:~2,'0D:~2,'0D~3,3FZ" year month date hour minute second deciseconds)))))
1500 (defun image-point-coordinates (image-data-alist global-point-coordinates)
1501 "Return a list (m n) of image coordinates representing
1502 global-point-coordinates in the image described in image-data-alist
1503 but scaled to fit into *image-size*."
1504 (ignore-errors
1505 (convert-image-coordinates
1506 (photogrammetry :reprojection
1507 image-data-alist
1508 (pairlis '(:x-global :y-global :z-global)
1509 (proj:cs2cs
1510 (list
1511 (proj:degrees-to-radians
1512 (coordinates-longitude global-point-coordinates))
1513 (proj:degrees-to-radians
1514 (coordinates-latitude global-point-coordinates))
1515 (coordinates-ellipsoid-height global-point-coordinates))
1516 :destination-cs (cdr (assoc :cartesian-system image-data-alist)))))
1517 image-data-alist)))
1519 (defun in-image-p (m n)
1520 "Check if m, n lay inside *image-size*."
1521 (and m n (<= 0 m (first *image-size*)) (<= 0 n (second *image-size*))))
1523 (defun-cached sections (table)
1524 "Return list of distinct pairs of vnk, nnk found in table in
1525 current database."
1526 (ignore-errors
1527 (query (:order-by (:select 'vnk 'nnk (:max 'nk-station)
1528 :from (intern (db-credentials-table *postgresql-road-network-credentials*))
1529 :where (:and (:not-null 'vnk) (:not-null 'nnk))
1530 :group-by 'vnk 'nnk)
1531 'vnk 'nnk))))
1534 (defun stations (table vnk nnk &optional (step 1))
1535 "Return a list of plists of :longitude, :latitude,
1536 :ellipsoid-height, :station, :azimuth of stations step metres apart
1537 between vnk and nnk."
1538 (when (and table vnk nnk)
1539 (let ((stations
1540 (prog2
1541 (with-open-file (s "ttt" :direction :output :if-exists :append :if-does-not-exist :create)
1542 (print (list (get-universal-time) "PRE-QUERY") s))
1543 (query
1544 (:order-by
1545 (:select (:as (:st_x 't1.the-geom) 'longitude)
1546 (:as (:st_y 't1.the-geom) 'latitude)
1547 (:as (:st_z 't1.the-geom) 'ellipsoid-height)
1548 (:as 't1.nk-station 'station)
1549 (:as (:st_azimuth 't1.the-geom 't2.the-geom) 'azimuth)
1550 :from (:as table 't1)
1551 :left-join (:as table 't2)
1552 :on (:and (:= 't1.nk-station (:- 't2.nk-station 1))
1553 (:= 't2.vnk vnk)
1554 (:= 't2.nnk nnk))
1555 :where (:and (:= 't1.vnk vnk)
1556 (:= 't1.nnk nnk)
1557 (:= 0 (:% 't1.nk-station step))))
1558 't1.nk-station)
1559 :plists)
1560 (with-open-file (s "ttt" :direction :output :if-exists :append :if-does-not-exist :create)
1561 (print (list (get-universal-time) "POST-QUERY") s)))))
1562 (setf
1563 (getf (nth (- (length stations) 1) stations) :azimuth)
1564 (getf (nth (- (length stations) 2) stations) :azimuth))
1565 stations)))
1567 (defun-cached all-stations (table vnk nnk)
1568 "Return a vector of coordinates of all points between vnk and nnk,
1569 station (in metres) being the vector index."
1570 (when (and table vnk nnk)
1571 (let* ((stations (stations table vnk nnk))
1572 (result (make-array (list (1+ (getf (first (last stations)) :station)))
1573 :initial-element nil)))
1574 (loop
1575 for i in stations
1576 do (destructuring-bind (&key longitude latitude ellipsoid-height station azimuth)
1578 (setf (svref result station)
1579 (make-coordinates :longitude longitude
1580 :latitude latitude
1581 :ellipsoid-height ellipsoid-height
1582 :azimuth azimuth))))
1583 result)))
1585 (defun-cached road-section-image-data (provenience-string table vnk nnk step rear-view-p)
1586 "Return a list of instances of image data corresponding to stations,
1587 which are step metres apart, found in table in current database.
1588 provenience-string only serves as a marker of the provenience of image
1589 data once cached."
1590 (remove nil ;; (mapcar #'(lambda (x)
1591 ;; (apply #'image-data :rear-view-p rear-view-p x))
1592 ;; (stations table vnk nnk step))
1593 (loop
1594 with azimuth-fallback = nil
1595 for station in (stations table vnk nnk step)
1596 when (not (eq (getf station :azimuth) :null))
1597 do (setf azimuth-fallback (getf station :azimuth))
1598 and collect (apply #'image-data :rear-view-p rear-view-p station)
1600 when (and azimuth-fallback
1601 (eq (getf station :azimuth) :null))
1602 do (setf (getf station :azimuth) azimuth-fallback)
1603 and collect (apply #'image-data :rear-view-p rear-view-p station))))
1605 (defun provenience-string (url)
1606 "Turn url recognisably into something suitable as part of a file
1607 name."
1608 (let ((parsed-url (puri:parse-uri url)))
1609 (format nil "~A_~A~{_~A~}"
1610 (puri:uri-host parsed-url)
1611 (puri:uri-port parsed-url)
1612 (cl-utilities:split-sequence
1613 #\/ (puri:uri-path parsed-url) :remove-empty-subseqs t))))
1616 (defun cache-file-name (kind &rest args)
1617 "Return pathname for a cache file distinguishable by kind and args."
1618 (make-pathname :directory *cache-dir*
1619 :name (format nil "~{~:[f~;~:*~(~A~)~]_~}~S.~S"
1620 args
1621 (fasttrack-version :major t)
1622 (fasttrack-version :minor t))
1623 :type (string-downcase kind)))
1625 (defun cache-images (road-section-image-data)
1626 "Download images described in road-section-image-data into their
1627 canonical places."
1628 (loop
1629 for i in road-section-image-data
1630 do (download-image i)))
1632 (defun get-image-data (road-section-image-data station step)
1633 "Return image data for the image near station."
1634 (find (* step (round station step)) road-section-image-data
1635 :key #'image-data-station
1636 :test #'=))
1638 (defun get-image-data-alist (road-section-image-data station step)
1639 "Return as an alist data for the image near station."
1640 (image-data-alist (get-image-data road-section-image-data station step)))
1642 (defun image-data (&key longitude latitude ellipsoid-height station azimuth rear-view-p)
1643 "Get from Phoros server image data for location near longitude,
1644 latitude."
1645 (ignore-errors
1646 (let* ((coordinates (make-coordinates :longitude longitude
1647 :latitude latitude
1648 :ellipsoid-height ellipsoid-height
1649 :azimuth azimuth))
1650 (image-data (phoros-nearest-image-data coordinates rear-view-p)))
1651 (when (image-data-p image-data)
1652 (setf (image-data-station image-data) station)
1653 (setf (image-data-station-coordinates image-data) coordinates)
1654 image-data))))
1656 (define-condition phoros-server-error (error)
1657 ((body :reader body :initarg :body)
1658 (status-code :reader status-code :initarg :status-code)
1659 (headers :reader headers :initarg :headers)
1660 (url :reader url :initarg :url)
1661 (reason-phrase :reader reason-phrase :initarg :reason-phrase))
1662 (:report (lambda (condition stream)
1663 (format stream "Can't connect to Phoros server: ~A (~D)"
1664 (reason-phrase condition) (status-code condition)))))
1666 (defun phoros-lib-url (canonical-url suffix)
1667 "Replace last path element of canonical-url by lib/<suffix>."
1668 (let* ((parsed-canonical-url (puri:parse-uri canonical-url))
1669 (old-path (puri:uri-parsed-path parsed-canonical-url))
1670 (new-path (append (butlast old-path) (list "lib" suffix)))
1671 (new-url (puri:copy-uri parsed-canonical-url)))
1672 (setf (puri:uri-parsed-path new-url) new-path)
1673 new-url))
1675 (defun phoros-login (url user-name user-password)
1676 "Log into Phoros server; return T if successful. Try logging out
1677 first."
1678 ;; (setf *phoros-url* url)
1679 (setf drakma:*allow-dotless-cookie-domains-p* t)
1680 (pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)
1681 (ignore-errors (phoros-logout))
1682 (setf *phoros-cookies* (make-instance 'drakma:cookie-jar))
1683 (multiple-value-bind (body status-code headers url stream must-close reason-phrase)
1684 (drakma:http-request (puri:parse-uri url) :cookie-jar *phoros-cookies*)
1685 (declare (ignore stream must-close))
1686 (assert (= status-code 200) ()
1687 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1688 (multiple-value-bind (body status-code headers authenticate-url stream must-close reason-phrase)
1689 (drakma:http-request (phoros-lib-url url "authenticate")
1690 :cookie-jar *phoros-cookies*
1691 :form-data t
1692 :method :post
1693 :parameters (pairlis '("user-name" "user-password")
1694 (list user-name user-password)))
1695 (declare (ignore stream must-close))
1696 (assert (< status-code 400) ()
1697 'phoros-server-error :body body :status-code status-code :headers headers :url authenticate-url :reason-phrase reason-phrase)
1698 (let ((body-strings (cl-utilities:split-sequence #\Space (substitute-if-not #\Space #'alphanumericp body))))
1699 (and (not (find "Rejected" body-strings :test #'string=))
1700 (not (find "Retry" body-strings :test #'string=))
1701 (= status-code 200)))))) ;should be 302 (?)
1703 (defun phoros-logout ()
1704 (multiple-value-bind (body status-code headers url stream must-close reason-phrase)
1705 (drakma:http-request (phoros-lib-url *phoros-url* "logout"))
1706 (declare (ignore stream must-close))
1707 (assert (= status-code 200) ()
1708 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)))
1710 (defun run-phoros-browser ()
1711 (when *road-section*
1712 (with-statusbar-message "calling browser synchronously"
1713 (destructuring-bind (table vnk nnk road-section-length)
1714 *road-section*
1715 (let ((current-coordinates (svref (all-stations table vnk nnk) (saved-station))))
1716 (uiop:run-program (format nil "firefox '~A/lib/set-cursor?bbox=~F,~F,~F,~F&longitude=~F&latitude=~F'"
1717 *phoros-url*
1718 (- (coordinates-longitude current-coordinates) .02)
1719 (- (coordinates-latitude current-coordinates) .01)
1720 (+ (coordinates-longitude current-coordinates) .02)
1721 (+ (coordinates-latitude current-coordinates) .01)
1722 (coordinates-longitude current-coordinates)
1723 (coordinates-latitude current-coordinates)
1725 (uiop:run-program (format nil "firefox '~A'" *phoros-url*)))))))
1728 (defun heading (azimuth rear-view-p)
1729 "Return as a string the one of east, west, north, south which best
1730 describes azimuth."
1731 (cond ((<= (* 1/4 pi) azimuth (* 3/4 pi)) (if rear-view-p "west" "east"))
1732 ((<= (* 3/4 pi) azimuth (* 5/4 pi)) (if rear-view-p "north" "south"))
1733 ((<= (* 5/4 pi) azimuth (* 7/4 pi)) (if rear-view-p "east" "west"))
1734 ((or (<= (* 5/4 pi) azimuth pi) (<= 0 (* 1/4 pi))) (if rear-view-p "south" "north"))))
1736 (defun phoros-nearest-image-data (coordinates rear-view-p)
1737 "Return a set of image-data."
1738 (multiple-value-bind (body status-code headers url stream must-close reason-phrase)
1739 (drakma:http-request (phoros-lib-url *phoros-url* "nearest-image-data")
1740 :cookie-jar *phoros-cookies*
1741 :method :post
1742 :content-type "text/plain; charset=UTF-8"
1743 :content (json:encode-json-plist-to-string (list :longitude (coordinates-longitude coordinates)
1744 :latitude (coordinates-latitude coordinates)
1745 :zoom 20
1746 :count 1
1747 :selected-restriction-ids (vector "Device_21" (heading (coordinates-azimuth coordinates) rear-view-p))))) ;TODO: document requirement for restrictions tagged north, east, south, west, and front_cam; actually use the latter
1748 (declare (ignore stream must-close))
1749 (assert (= status-code 200) ()
1750 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1751 (unless (string-equal body "null")
1752 (apply #'make-image-data :allow-other-keys t
1753 (plist-from-alist
1754 (car (json:decode-json-from-string body)))))))
1756 (defun download-file (url path)
1757 "Unless already there, store content from url under path. Return
1758 nil if nothing needed storing."
1759 (when path
1760 (ensure-directories-exist path)
1761 (with-open-file (file-stream path :direction :output
1762 :element-type 'unsigned-byte
1763 :if-exists nil)
1764 (when file-stream
1765 (multiple-value-bind
1766 (body status-code headers url stream must-close reason-phrase)
1767 (drakma:http-request url
1768 :cookie-jar *phoros-cookies*
1769 :method :get)
1770 (declare (ignore stream must-close))
1771 (setf *t* url)
1772 (assert (= status-code 200) ()
1773 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1774 (write-sequence body file-stream)
1775 reason-phrase)))))
1777 (defun download-image (image-data)
1778 "If not already there, download a png image, shrink it, convert it
1779 into jpg, and store it under the cache path. Return that path."
1780 (multiple-value-bind (url origin-path destination-path)
1781 (image-url image-data)
1782 (when destination-path
1783 (unless (probe-file destination-path)
1784 (download-file url origin-path)
1785 (apply #'convert-image-file origin-path destination-path *image-size*)
1786 (delete-file origin-path))
1787 destination-path)))
1789 ;; (defun image-launched-p (image-data rear-view-p)
1790 ;; "Check if the image belonging to image-data is the current image."
1791 ;; (multiple-value-bind (url origin-path destination-path)
1792 ;; (image-url image-data)
1793 ;; (let ((remembered-image (if rear-view-p
1794 ;; *rear-view-image-path*
1795 ;; *front-view-image-path*)))
1796 ;; (when (and destination-path remembered-image)
1797 ;; (string= (namestring destination-path) (namestring remembered-image))))))
1799 ;; (defun remember-image-being-launched (image-data rear-view-p)
1800 ;; (multiple-value-bind (url origin-path destination-path)
1801 ;; (image-url image-data)
1802 ;; (if rear-view-p
1803 ;; (setf *rear-view-image-path* destination-path)
1804 ;; (setf *front-view-image-path* destination-path))))
1805 (defun remember-image-being-launched (image-data image-arrow-coordinates rear-view-p)
1806 (if rear-view-p
1807 (progn
1808 (setf *rear-view-image-data* image-data)
1809 (setf *rear-view-image-arrow-coordinates* image-arrow-coordinates))
1810 (progn
1811 (setf *front-view-image-data* image-data)
1812 (setf *front-view-image-arrow-coordinates* image-arrow-coordinates))))
1814 ;; (defun forget-images-being-launched ()
1815 ;; (setf *rear-view-image-path* "")
1816 ;; (setf *front-view-image-path* ""))
1817 (defun forget-images-being-launched ()
1818 (setf *rear-view-image-data* nil)
1819 (setf *front-view-image-data* nil))
1821 (defun image-data-alist (image-data)
1822 "Return an alist representation of image-data."
1823 (when image-data
1824 (loop
1825 for i in (append (mapcar #'ensure-hyphen-before-digit *aggregate-view-columns*) '(station station-coordinates))
1826 collect (intern (string i) 'keyword) into keys
1827 collect (funcall (intern (concatenate 'string (string 'image-data-)
1828 (string i)))
1829 image-data)
1830 into values
1831 finally (return (pairlis keys values)))))
1833 (defun plist-from-alist (alist)
1834 (loop
1835 for (key . value) in alist
1836 collect key
1837 collect value))
1839 (defun image-url (image-data)
1840 "Return an image URL made from ingredients found in image-data, the
1841 corresponding cache path, and the corresponding cache path for the
1842 shrunk image."
1843 (when image-data
1844 (let* ((path
1845 (format nil "~A/~A/~A/~D.png"
1846 (puri:uri-path (phoros-lib-url *phoros-url* "photo"))
1847 (image-data-directory image-data)
1848 (image-data-filename image-data)
1849 (image-data-byte-position image-data)))
1850 (query
1851 (format nil "mounting-angle=~D~
1852 &bayer-pattern=~{~D~#^,~}~
1853 &color-raiser=~{~D~#^,~}"
1854 (image-data-mounting-angle image-data)
1855 (map 'list #'identity (image-data-bayer-pattern image-data))
1856 (map 'list #'identity (image-data-color-raiser image-data))))
1857 (url (puri:copy-uri (puri:parse-uri *phoros-url*) :path path :query query))
1858 (host (puri:uri-host url))
1859 (port (puri:uri-port url))
1860 (cache-directory (append *cache-dir*
1861 (list (format nil "~A_~D" host port))
1862 (cdr (pathname-directory (puri:uri-path url)))))
1863 (cache-name (pathname-name (puri:uri-path url)))
1864 (cache-type (pathname-type (puri:uri-path url))))
1865 (values url
1866 (make-pathname :directory cache-directory
1867 :name cache-name
1868 :type cache-type)
1869 (make-pathname :directory cache-directory
1870 :name cache-name
1871 :type "jpg")))))
1873 (defun convert-image-file (origin-file destination-file width height)
1874 "Convert origin-file into destination-file of a maximum size of
1875 width x height."
1876 (handler-case
1877 (lisp-magick-wand:with-magick-wand (wand :load (namestring origin-file))
1878 (let ((a (/ (lisp-magick-wand:get-image-width wand)
1879 (lisp-magick-wand:get-image-height wand))))
1880 (if (> a (/ width height))
1881 (lisp-magick-wand:scale-image wand width (truncate (/ width a)))
1882 (lisp-magick-wand:scale-image wand (truncate (* a height)) height)))
1883 (lisp-magick-wand:write-image wand (namestring destination-file)))
1884 (lisp-magick-wand:magick-wand-error ()))) ;ignore
1886 (defun convert-image-coordinates (original-coordinates-alist image-data-alist)
1887 "Convert image coordinates from original-coordinates-alist for the
1888 image in image-data-alist into a list of coordinates for that image
1889 scaled and centered to *image-size*."
1890 (let* ((original-m (cdr (assoc :m original-coordinates-alist)))
1891 (original-n (cdr (assoc :n original-coordinates-alist)))
1892 (original-width (cdr (assoc :sensor-width-pix image-data-alist)))
1893 (original-height (cdr (assoc :sensor-height-pix image-data-alist)))
1894 (new-width (first *image-size*))
1895 (new-height (second *image-size*))
1896 (scaling-factor (min (/ new-width original-width) (/ new-height original-height)))
1897 (new-m-offset (/ (- new-width (* original-width scaling-factor)) 2))
1898 (new-n-offset (/ (- new-height (* original-height scaling-factor)) 2))
1899 (new-m (+ (* original-m scaling-factor) new-m-offset))
1900 (new-n (- new-height ;flip n
1901 (+ (* original-n scaling-factor) new-n-offset))))
1902 (mapcar #'round (list new-m new-n))))
1904 (defun put-image (&key vnk nnk station step rear-view-p)
1905 "Put an image along with a labelled station marker on screen."
1906 (when (and vnk nnk station)
1907 (with-connection *postgresql-road-network-credentials*
1908 (setf station (or station 0))
1909 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1910 (point-radius 5)
1911 (image-widget (if rear-view-p "img_rearview" "img_frontview"))
1912 (drawing-widget (if rear-view-p "draw_rearview" "draw_frontview"))
1913 (spinner-widget (if rear-view-p "spinner_rearview" "spinner_frontview"))
1914 (time-widget (if rear-view-p "rear_view_time" "front_view_time"))
1915 global-point-coordinates
1916 image-data-alist
1917 image-arrow-coordinates
1918 global-point-coordinates-thread)
1919 (setf global-point-coordinates-thread
1920 (bt:make-thread
1921 (lambda ()
1922 (with-connection *postgresql-road-network-credentials*
1923 (setf global-point-coordinates
1924 (subseq (all-stations table vnk nnk :message (list vnk nnk))
1925 (min (length (all-stations table vnk nnk)) station)
1926 (min (length (all-stations table vnk nnk)) (+ station 4))))))
1927 :name "global-point-coordinates"))
1928 (bt:join-thread global-point-coordinates-thread)
1929 (setf image-data-alist
1930 (get-image-data-alist (road-section-image-data (provenience-string *phoros-url*) table vnk nnk step rear-view-p :message (list "get-image-data-alist" vnk nnk (if rear-view-p "rear-view" "front-view")))
1931 station
1932 step))
1933 (setf image-arrow-coordinates
1934 (loop
1935 for i across global-point-coordinates
1936 append (image-point-coordinates image-data-alist i)))
1937 (setf image-label-coordinates (ignore-errors
1938 (list (- (first image-arrow-coordinates) point-radius)
1939 (- (second image-arrow-coordinates) point-radius))))
1940 (setf image-data (get-image-data (road-section-image-data (provenience-string *phoros-url*) table vnk nnk step rear-view-p :message (list "image-data" vnk nnk (if rear-view-p "rear-view" "front-view"))) station step))
1941 (remember-image-being-launched image-data image-arrow-coordinates rear-view-p)))))
1943 (defun jump-to-station (station)
1944 (unless *road-section* (return-from jump-to-station))
1945 (save-station station) ;picked up by jump-to-station-worker
1946 ;; (clear-main-window)
1949 (defun jump-to-station-worker ()
1950 (let ((current-station))
1951 (loop
1952 (if (eql current-station *station*)
1953 (bt:thread-yield)
1954 (progn
1955 (setf current-station *station*)
1956 (handler-case
1957 (let ((table (first *road-section*))
1958 (vnk (second *road-section*))
1959 (nnk (third *road-section*)))
1960 (pipeglade-out "station" "set_text" current-station)
1961 (place-chart-cursor current-station)
1962 (if *show-rear-view-p*
1963 (put-image :vnk vnk :nnk nnk :station current-station :step 10 :rear-view-p t)
1964 (progn
1965 ;; (pipeglade-out "draw_rearview" "remove" 2)
1966 (pipeglade-out "img_rearview" "set_from_file" "public_html/phoros-logo-background.png")))
1967 (if *show-front-view-p*
1968 (put-image :vnk vnk :nnk nnk :station current-station :step 10 :rear-view-p nil)
1969 (progn
1970 ;; (pipeglade-out "draw_frontview" "remove" 2)
1971 (pipeglade-out "img_frontview" "set_from_file" "public_html/phoros-logo-background.png")))
1972 (put-text-values vnk nnk current-station))
1973 (database-connection-error ())))))))
1975 (defun cruise-control (&key backwardp)
1976 (setf *cruise-control-backward-p* backwardp)
1977 (setf *cruise-control* t)) ;picked up by cruise-control-worker
1979 (defun stop-cruise-control ()
1980 (setf *cruise-control* nil))
1982 (defun cruise-control-worker ()
1983 (loop
1984 (let ((road-section-length (fourth *road-section*)))
1985 (if (and *cruise-control*
1986 *rear-view-image-done*
1987 *front-view-image-done*
1988 (<= (+ 0 *big-step*) *station* (- road-section-length *big-step*)))
1989 (progn
1990 (setf *rear-view-image-done* nil)
1991 (setf *front-view-image-done* nil)
1992 (pipeglade-out "station_scale" "set_value" (+ (if *cruise-control-backward-p* (- *big-step*) *big-step*) *station*)))
1993 (progn
1994 (bt:thread-yield))))))