Handle errors on pathologic photogrammetry input
[phoros.git] / fasttrack.lisp
blob05b959c5ee7c7ca18d6c94596de609cada7d04d2
1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2012, 2016, 2017 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))
39 (database " ")
40 (user " ")
41 (password " ")
42 (host " ")
43 (port-key :port :read-only t)
44 (port 5432)
45 (ssl-key :use-ssl :read-only t)
46 (ssl :no)
47 (modifiedp-key :modifiedp :read-only t)
48 (modifiedp t)
49 (table-key :table :read-only t)
50 (table " ")
51 (allow-other-keys-key :allow-other-keys :read-only t)
52 (allow-other-keys-value t :read-only t))
54 (defvar *postgresql-road-network-credentials* (make-db-credentials)
55 "A list: (database user password host :port 5432 :use-ssl ssl-p.")
57 (defvar *postgresql-zeb-credentials* (make-db-credentials)
58 "A list: (database user password host :port 5432 use-ssl :ssl-p.")
60 (defvar *road-network-chart-configuration* nil
61 "Database columns selected for rendering.")
63 (defvar *zeb-chart-configuration* nil
64 "Database columns selected for rendering.")
66 (defvar *accidents-chart-configuration* (list nil nil nil)
67 "Accidents rendering parameters.")
69 (defvar *postgresql-accidents-credentials* (make-db-credentials)
70 "A list: (database user password host &key :port 5432 :use-ssl ssl-p.")
72 (defvar *postgresql-road-network-ok* nil
73 "t if database connection could be established.")
75 (defvar *postgresql-zeb-ok* nil
76 "t if database connection could be established.")
78 (defvar *postgresql-accidents-ok* nil
79 "t if database connection could be established.")
81 (defvar *station* 0
82 "Current station.")
84 (defvar *road-section* nil
85 "If there is a chart, we store a list of its parameters (table vnk
86 nnk road-section-length) here.")
88 (defvar *road-section-raw-data* (make-hash-table :size 97)
89 "Undigested selected row numbers from road section dialog")
91 (defvar *road-section-selection* '()
92 "Row numbers of the road sections selected for processing.")
94 (defvar *road-network-chart-raw-data* (make-hash-table :size 997 :test #'equal)
95 "Raw messages from the road network part of the chart dialog")
97 (defvar *accidents-chart-raw-data* (list nil nil nil)
98 "Undigested input from the accidents part of the chart dialog.")
100 (defvar *zeb-chart-raw-data* (make-hash-table :size 997 :test #'equal)
101 "Raw messages from road section dialog")
103 (defparameter *aggregate-view-columns*
104 (list 'usable
105 'recorded-device-id ;debug
106 'device-stage-of-life-id ;debug
107 'generic-device-id ;debug
108 'directory
109 'measurement-id
110 'filename 'byte-position 'point-id
111 'trigger-time
112 ;;'coordinates ;the search target
113 'longitude 'latitude 'ellipsoid-height
114 'cartesian-system
115 'east-sd 'north-sd 'height-sd
116 'roll 'pitch 'heading
117 'roll-sd 'pitch-sd 'heading-sd
118 'sensor-width-pix 'sensor-height-pix
119 'pix-size
120 'bayer-pattern 'color-raiser
121 'mounting-angle
122 'dx 'dy 'dz 'omega 'phi 'kappa
123 'c 'xh 'yh 'a1 'a2 'a3 'b1 'b2 'c1 'c2 'r0
124 'b-dx 'b-dy 'b-dz 'b-rotx 'b-roty 'b-rotz
125 'b-ddx 'b-ddy 'b-ddz
126 'b-drotx 'b-droty 'b-drotz)
127 "Most of the column names of aggregate-view.")
129 (defvar *phoros-cookies* nil
130 "Container for cookies sent by Phoros server")
132 (defvar *phoros-url* nil
133 "URL of the Phoros project currently in use.")
135 (defvar *phoros-credentials* '("user" "password")
136 "List of (user password) used for login at *phoros-url*.")
138 (defvar *cache-dir* '(:relative "cache"))
140 (defparameter *image-size* '(800 700)
141 "Image size in pixels in a list (width height).")
143 (defparameter *chart-height* 155
144 "Height of chart in pixels.")
146 (defparameter *chart-fringe* 20
147 "Lower, uncharted part of chart.")
149 (defparameter *chart-tail* 200
150 "Rightmost, uncharted part of chart.")
152 (defparameter *scale-distance* 40
153 "Horizontal distance between two scales.")
155 (defvar *cruise-control* nil)
156 (defvar *cruise-control-backward-p* nil)
158 (defvar *rear-view-image-done* nil)
159 (defvar *front-view-image-done* nil)
161 (defparameter *caching-images-p* nil)
163 (defvar *pipeglade-pid-file* "fasttrack-pipeglade.pid")
165 (defparameter *cursor-color* "orange"
166 "Color of cursor in both chart and images.")
168 (defparameter *big-step* 10
169 "Station increment/decrement.")
171 (defparameter *pipeglade-out-lock* (bt:make-lock))
172 (defparameter *pipeglade-out-fifo* "in.fifo")
173 (defparameter *pipeglade-in-fifo* "out.fifo")
175 (defun pipeglade-out (widget action &rest data)
176 "Send a pipeglade command to UI."
177 (bt:with-lock-held (*pipeglade-out-lock*)
178 (with-open-file (out *pipeglade-out-fifo*
179 :direction :output
180 :if-exists :append
181 :if-does-not-exist :error)
182 (format out "~A:~A~{ ~@[~A~]~}~%" widget action data))))
184 (defun ensure-hyphen-before-digit (symbol)
185 "Return symbol with hyphens inserted after each letter that is
186 followed by a digit. "
187 (intern
188 (coerce
189 (loop
190 with need-hyphen-before-next-digit-p
191 for c across (string symbol)
192 if (and need-hyphen-before-next-digit-p (digit-char-p c))
193 collect #\- and collect c and do (setf need-hyphen-before-next-digit-p nil)
194 else collect c and do (setf need-hyphen-before-next-digit-p nil)
196 if (alpha-char-p c) do (setf need-hyphen-before-next-digit-p t) end)
197 'string)))
199 (defmacro with-statusbar-message (message &body body)
200 "Push message to statusbar while body is executing."
201 `(unwind-protect
202 (progn
203 (pipeglade-out "statusbar" "push_id" (sxhash ,message) ,message)
204 ,@body)
205 (pipeglade-out "statusbar" "pop_id" (sxhash ,message))))
207 (defmacro with-spinner (spinner &body body)
208 "Let spinner spin while body is executing."
209 `(unwind-protect
210 (progn
211 (pipeglade-out ,spinner "start")
212 ,@body)
213 (pipeglade-out ,spinner "stop")))
215 (defmacro defun-cached (name (&rest args) &body body &aux (doc ""))
216 "Define a function whose return value must be readibly printable, is
217 being read from a chache if possible, and is being cached if
218 necessary. The function defined has a secondary return value
219 cached-p. If function is called with :from-cache-only t, let it
220 return nil and nil if there is nothing cached. If function is
221 called with a :message keyarg, a pretty-printed version will be
222 shown as part of the statusbar message."
223 (when (stringp (car body))
224 (setf doc (car body))
225 (setf body (cdr body)))
226 (cl-utilities:with-unique-names (input-stream output-stream)
227 `(defun ,name (,@args &key from-cache-only create-fresh-cache message)
228 ,doc
229 (flet ((read-from-cache ()
230 (with-open-file (,input-stream (cache-file-name ',name ,@args)
231 :direction :input
232 :if-does-not-exist :error)
233 (values (read ,input-stream) t)))
234 (run-and-cache ()
235 (values (with-statusbar-message (format nil "populating cache [~(~A~)~@[ ~A~]]" ',name message)
236 (with-open-file (,output-stream (cache-file-name ',name ,@args)
237 :direction :output
238 :if-exists :supersede)
239 (prin1 (progn ,@body)
240 ,output-stream)))
241 nil)))
242 (ensure-directories-exist (cache-file-name ',name ,@args))
244 (handler-bind
245 ((file-error (lambda (c)
246 (invoke-restart 'restart-create-fresh-cache "FILE")))
247 (end-of-file (lambda (c)
248 (invoke-restart 'restart-create-fresh-cache "EOF"))))
249 (restart-case (if create-fresh-cache
250 (run-and-cache)
251 (read-from-cache))
252 (restart-create-fresh-cache (para)
253 (if from-cache-only
254 (values nil nil)
255 (,name ,@args :create-fresh-cache t :message message)))))))))
257 (defun empty-image-data-p (image-data)
258 (and (not (image-data-station image-data))
259 (empty-coordinates-p (image-data-station-coordinates image-data))))
261 (defun empty-coordinates-p (coordinates)
262 (not (or (coordinates-longitude coordinates)
263 (coordinates-latitude coordinates)
264 (coordinates-ellipsoid-height coordinates)
265 (coordinates-azimuth coordinates))))
267 (defun image-data-equal (i1 i2)
268 (and (eql (image-data-station i1) (image-data-station i2))
269 (coordinates-equal (image-data-station-coordinates i1) (image-data-station-coordinates i2))
270 (equal (image-data-filename i1) (image-data-filename i2))
271 (eql (image-data-byte-position i1) (image-data-byte-position i2))))
273 (defun coordinates-equal (c1 c2)
274 (and (eql (coordinates-longitude c1) (coordinates-longitude c2))
275 (eql (coordinates-latitude c1) (coordinates-latitude c2))
276 (eql (coordinates-ellipsoid-height c1) (coordinates-ellipsoid-height c2))
277 (eql (coordinates-azimuth c1) (coordinates-azimuth c2))))
279 (defun display-date-and-image (time-widget img-widget draw-widget spinner-widget image-data)
280 "Display image and its trigger time on UI. Return the time the UI
281 is estimated to take."
282 (let ((sleep-duration 0))
283 (with-spinner spinner-widget
284 (pipeglade-out time-widget "set_text" (iso-time (image-data-trigger-time image-data)))
285 (handler-case
286 (let ((image-filename (namestring (download-image image-data))))
287 (if image-filename
288 (progn
289 (pipeglade-out draw-widget "remove" 2)
290 (pipeglade-out img-widget "set_from_file" image-filename)
291 (setf sleep-duration .3))
292 (progn
293 (pipeglade-out img-widget "set_from_file" "public_html/phoros-logo-background.png")
294 (setf sleep-duration .1))))
295 (phoros-server-error ()
296 (pipeglade-out draw-widget "remove" 2)
297 (pipeglade-out img-widget "set_from_file" "public_html/phoros-logo-background.png")
298 (setf sleep-duration 1)))
299 sleep-duration)))
301 (defun clear-date-image-and-arrow (time-widget img-widget draw-widget)
302 (pipeglade-out img-widget "set_from_file" "public_html/phoros-logo-background.png")
303 (pipeglade-out time-widget "set_text")
304 (pipeglade-out draw-widget "remove" 2))
306 (defun display-image-arrow (draw-widget image-arrow-coordinates station)
307 "Display a station marker in the image on UI. Return the time the
308 UI is estimated to take."
309 (if image-arrow-coordinates
310 (let* ((point-radius 5)
311 (image-label-coordinates (ignore-errors
312 (list (- (first image-arrow-coordinates) point-radius)
313 (- (second image-arrow-coordinates) point-radius)))))
314 (pipeglade-out draw-widget "remove" 2)
315 (pipeglade-out draw-widget "move_to" 2 (first image-arrow-coordinates) (second image-arrow-coordinates))
316 (pipeglade-out draw-widget "line_to" 2 (first (last image-arrow-coordinates 2)) (second (last image-arrow-coordinates 2)))
317 (pipeglade-out draw-widget "stroke" 2)
318 (pipeglade-out draw-widget "arc" 2 (first image-arrow-coordinates) (second image-arrow-coordinates) point-radius 0 360)
319 (pipeglade-out draw-widget "stroke" 2)
320 (pipeglade-out draw-widget "move_to" 2 (first image-label-coordinates) (second image-label-coordinates))
321 (pipeglade-out draw-widget "rel_move_for" 2 "se" station)
322 (pipeglade-out draw-widget "show_text" 2 station)
324 (progn
325 (pipeglade-out draw-widget "remove" 2)
326 0)))
328 (defmacro image-worker (view-direction)
329 (let (global-image-data global-image-arrow-coordinates global-image-done time-widget spinner-widget draw-widget img-widget)
330 (ecase view-direction
331 (:rear-view
332 (setf global-image-data '*rear-view-image-data*)
333 (setf global-image-arrow-coordinates '*rear-view-image-arrow-coordinates*)
334 (setf global-image-done '*rear-view-image-done*)
335 (setf time-widget "rear_view_time")
336 (setf spinner-widget "spinner_rearview")
337 (setf draw-widget "draw_rearview")
338 (setf img-widget "img_rearview"))
339 (:front-view
340 (setf global-image-data '*front-view-image-data*)
341 (setf global-image-arrow-coordinates '*front-view-image-arrow-coordinates*)
342 (setf global-image-done '*front-view-image-done*)
343 (setf time-widget "front_view_time")
344 (setf spinner-widget "spinner_frontview")
345 (setf draw-widget "draw_frontview")
346 (setf img-widget "img_frontview")))
347 (cl-utilities:with-unique-names (current-image-data
348 current-station
349 current-image-arrow-coordinates
350 current-road-section
351 station
352 road-section
353 image-data
354 image-arrow-coordinates
355 sleep-duration
356 point-radius
357 image-filename
358 image-label-coordinates)
359 `(lambda ()
360 (let ((current-image-data *empty-image-data*)
361 (current-station 0)
362 (current-road-section nil)
363 (current-image-arrow-coordinates nil))
364 (loop
365 (let ((station *station*)
366 (road-section *road-section*)
367 (image-data ,global-image-data)
368 (image-arrow-coordinates ,global-image-arrow-coordinates)
369 (sleep-duration 0))
370 (block image-worker
371 (block image-output
372 (if (image-data-equal current-image-data image-data)
373 (if (and (eql current-station station)
374 (equal current-road-section road-section))
375 (progn
376 (incf sleep-duration .1)
377 (bt:thread-yield)
378 (return-from image-worker))
379 (progn
380 (psetf current-station station
381 current-road-section road-section)
382 (return-from image-output)))
383 (progn
384 (psetf current-image-data image-data
385 current-station station
386 current-road-section road-section)
387 (if (empty-image-data-p image-data)
388 (progn
389 (clear-date-image-and-arrow ,time-widget ,img-widget ,draw-widget)
390 (incf sleep-duration .1)
391 (return-from image-worker))
392 (incf sleep-duration
393 (display-date-and-image ,time-widget ,img-widget ,draw-widget ,spinner-widget image-data))))))
394 (if (equal current-image-arrow-coordinates image-arrow-coordinates)
395 (progn
396 (incf sleep-duration .1)
397 (return-from image-worker))
398 (progn
399 (setf current-image-arrow-coordinates image-arrow-coordinates)
400 (incf sleep-duration
401 (display-image-arrow ,draw-widget image-arrow-coordinates station)))))
402 (sleep sleep-duration)
403 (setf ,global-image-done t))))))))
405 (eval '(defstruct coordinates
406 longitude
407 latitude
408 ellipsoid-height
409 azimuth))
411 (eval `(defstruct image-data
412 ;; fasttrack auxiliary slots
413 station
414 station-coordinates
415 (rear-view-p nil)
416 ;; original Phoros image data slots
417 ,@(mapcar #'ensure-hyphen-before-digit *aggregate-view-columns*)))
419 (defparameter *empty-coordinates*
420 (make-coordinates :longitude nil
421 :latitude nil
422 :ellipsoid-height nil
423 :azimuth nil)
424 "Representation of a zero value for coordinates.")
426 (defparameter *empty-image-data*
427 (make-image-data :station nil
428 :station-coordinates *empty-coordinates*)
429 "Representation of a zero value for image-data.")
431 (defvar *rear-view-image-data* *empty-image-data*
432 "The currently displayed image.")
434 (defvar *front-view-image-data* *empty-image-data*
435 "The currently displayed image.")
437 (defvar *rear-view-image-arrow-coordinates* nil)
438 (defvar *front-view-image-arrow-coordinates* nil)
440 (defvar *show-rear-view-p* t)
442 (defvar *show-front-view-p* t)
444 (defun start-pipeglade ()
445 (let* ((stale-pipeglade-pid
446 (with-open-file (stream *pipeglade-pid-file*
447 :direction :input :if-does-not-exist :create)
448 (read stream nil)))
449 (stale-pipeglade-program-name
450 (uiop:run-program (format nil "ps -p ~A -o comm=" stale-pipeglade-pid) :output :string :ignore-error-status t))
451 (length (min (length "pipeglade") (length stale-pipeglade-program-name))))
452 (when (string= "pipeglade" stale-pipeglade-program-name :end2 length)
453 (uiop:run-program (format nil "kill ~A" stale-pipeglade-pid))))
454 (let ((pipeglade-args "-i in.fifo -o out.fifo -u fasttrack.ui -b --name fasttrack --class Phoros"))
455 (loop
456 for i in '("./pipeglade" "~/pipeglade/pipeglade" "pipeglade")
457 until (probe-file i)
458 finally (uiop:run-program (format nil "~A ~A" i pipeglade-args) :output *pipeglade-pid-file*))))
460 (defun version-number-parts (dotted-string)
461 "Return the three version number components of something like
462 \"11.22.33\"."
463 (when dotted-string
464 (values-list (mapcar #'parse-integer
465 (cl-utilities:split-sequence #\. dotted-string)))))
467 (defun fasttrack-version (&key major minor revision)
468 "Return version of this program, either one integer part as denoted by
469 the key argument, or the whole dotted string."
470 (multiple-value-bind (major-number minor-number revision-number)
471 (version-number-parts *fasttrack-version*)
472 (cond (major major-number)
473 (minor minor-number)
474 (revision revision-number)
475 (t *fasttrack-version*))))
477 (defun check-dependencies ()
478 "Say OK if the necessary external dependencies are available."
479 (handler-case
480 (progn
481 (let ((utm-coordinate-system
482 (format nil "+proj=utm +ellps=WGS84 +zone=~D" 33)))
483 (proj:cs2cs (list (proj:degrees-to-radians 12)
484 (proj:degrees-to-radians 52) 0)
485 :destination-cs utm-coordinate-system))
486 (phoros-photogrammetry:del-all) ;check photogrammetry
487 (uiop:run-program (format nil "convert -version"))
488 (format *error-output* "~&dependencies OK~%"))
489 (error (e) (format *error-output* "while checking dependencies: ~A~&" e))))
491 (defun main ()
492 (handler-case
493 (progn
494 (in-package #:phoros-fasttrack) ;for reading of cached #S(...) forms
495 (cffi:use-foreign-library phoml)
496 (start-pipeglade)
497 (check-dependencies)
498 (restore-road-network-credentials)
499 (restore-zeb-credentials)
500 (restore-accidents-credentials)
501 (restore-phoros-credentials)
502 (restore-road-network-chart-configuration)
503 (restore-zeb-chart-configuration)
504 (restore-accidents-chart-configuration)
505 (restore-road-section)
506 (update-credentials-dialog)
507 ;; Kludge: tickle the dialog to make spinbuttons receptive
508 (pipeglade-out "chart_configuration" "set_visible" 1)
509 (pipeglade-out "chart_configuration" "set_visible" 0)
510 (pipeglade-out "chart_road_network" "set_line_cap" 1 "round")
511 (pipeglade-out "chart_road_network" "set_line_join" 1 "round")
512 (pipeglade-out "chart_zeb" "set_line_cap" 1 "round")
513 (pipeglade-out "chart_zeb" "set_line_join" 1 "round")
514 (pipeglade-out "chart_accidents" "set_line_join" 1 "miter")
515 (pipeglade-out "chart_accidents" "set_line_width" 1 1)
516 (pipeglade-out "chart_cursor" "set_source_rgba" 1 *cursor-color*)
517 (pipeglade-out "chart_cursor" "set_line_width" 1 3)
518 (pipeglade-out "chart_cursor" "set_dash" 1 3)
519 (pipeglade-out "chart_cursor" "set_font_size" 1 10)
520 (pipeglade-out "chart_road_network_scale" "set_font_size" 1 10)
521 (pipeglade-out "zeb_network_scale" "set_font_size" 1 10)
522 (dolist (image '("img_rearview" "img_frontview"))
523 (pipeglade-out image "set_from_file" "public_html/phoros-logo-background.png"))
524 (dolist (drawing-area '("draw_rearview" "draw_frontview"))
525 (pipeglade-out drawing-area "set_source_rgba" 1 *cursor-color*)
526 (pipeglade-out drawing-area "set_line_cap" 1 "round")
527 (pipeglade-out drawing-area "set_line_width" 1 2)
528 (pipeglade-out drawing-area "set_font_size" 1 10)
529 (pipeglade-out "version" "set_text" "version" *phoros-version*))
530 (with-open-file (in *pipeglade-in-fifo*
531 :direction :input
532 :if-does-not-exist :error)
533 (bt:make-thread
534 (image-worker :rear-view)
535 :name "rear-view-image-worker")
536 (bt:make-thread
537 (image-worker :front-view)
538 :name "front-view-image-worker")
539 (bt:make-thread
540 #'jump-to-station-worker
541 :name "jump-to-station-worker")
542 (bt:make-thread
543 #'cruise-control-worker
544 :name "cruise-control-worker")
545 (check-credentials-dialog-statuses)
546 (handler-case
547 (when *phoros-url*
548 (apply #'phoros-login *phoros-url* *phoros-credentials*))
549 (phoros-server-error ()))
550 ;; getting rid of initial feedback from credentials dialog:
551 (with-statusbar-message "please wait" (sleep 1))
552 (clear-input in)
553 (populate-road-section-dialog)
554 (restore-road-section-image-counts)
555 (restore-road-section-selection)
556 (update-road-section-selection)
557 ;; (set-road-section)
558 (populate-chart-dialog)
559 (prepare-chart)
560 (update-station (saved-station))
561 (with-statusbar-message "starting browser"
562 (uiop:run-program (format nil "firefox '~A' &" *phoros-url*)))
563 (loop
564 for message = (read-line in nil)
566 (cond
567 ((message-name= "quit" message)
568 (pipeglade-out "_" "main_quit")
569 (loop-finish))
570 ((and (message-name= "main" message)
571 (string= (message-info message) "closed"))
572 (pipeglade-out "_" "main_quit")
573 (loop-finish))
574 ((message-name= "station_scale" message)
575 (setf *station* (parse-integer (message-data message) :junk-allowed t)) ;picked up by jump-to-station-worker
576 (save-place *station* 'station))
577 ((message-name= "show_road_network_chart" message)
578 (pipeglade-out "chart_road_network" "set_visible" (message-info message))
579 (pipeglade-out "chart_road_network_scale" "set_visible" (message-info message)))
580 ((message-name= "show_zeb_chart" message)
581 (pipeglade-out "chart_zeb" "set_visible" (message-info message))
582 (pipeglade-out "chart_zeb_scale" "set_visible" (message-info message)))
583 ((message-name= "show_accidents_chart" message)
584 (pipeglade-out "chart_accidents" "set_visible" (message-info message)))
585 ((message-name= "show_rear_view" message)
586 (setf *show-rear-view-p* (string= (message-info message) "1")))
587 ((message-name= "show_front_view" message)
588 (setf *show-front-view-p* (string= (message-info message) "1")))
589 ((message-name= "big_step" message)
590 (let* ((step (parse-integer (message-data message) :junk-allowed t))
591 (label-text (format nil "~D m" step)))
592 (pipeglade-out "back" "set_label" label-text)
593 (pipeglade-out "forward" "set_label" label-text)
594 (pipeglade-out "big_step_back" "set_label" label-text)
595 (pipeglade-out "big_step_forward" "set_label" label-text)
596 (pipeglade-out "station_scale" "set_increments" 1 step)
597 (setf *big-step* step)))
598 ((message-name= "step_back" message)
599 (stop-cruise-control)
600 (update-station (1- (saved-station))))
601 ((message-name= "step_forward" message)
602 (stop-cruise-control)
603 (update-station (1+ (saved-station))))
604 ((message-name= "big_step_back" message)
605 (stop-cruise-control)
606 (update-station (- (saved-station) *big-step*)))
607 ((message-name= "big_step_forward" message)
608 (stop-cruise-control)
609 (update-station (+ (saved-station) *big-step*)))
610 ((message-name= "back" message)
611 (stop-cruise-control)
612 (cruise-control :backwardp t))
613 ((message-name= "forward" message)
614 (stop-cruise-control)
615 (cruise-control :backwardp nil))
616 ((message-name= "stop" message)
617 (stop-cruise-control))
618 ((message-name= "first_section" message)
619 (stop-cruise-control)
620 (set-road-section :direction :first)
621 (prepare-chart)
622 (update-station 0))
623 ((message-name= "previous_section" message)
624 (stop-cruise-control)
625 (set-road-section :direction :predecessor)
626 (prepare-chart)
627 (update-station 0))
628 ((message-name= "next_section" message)
629 (stop-cruise-control)
630 (set-road-section :direction :successor)
631 (prepare-chart)
632 (update-station 0))
633 ((message-name= "last_section" message)
634 (stop-cruise-control)
635 (set-road-section :direction :last)
636 (prepare-chart)
637 (update-station 0))
638 ((message-name= "road_sections" message)
639 (collect-road-section-select message))
640 ((message-name= "road_section_ok" message)
641 (digest-road-section-raw-data)
642 (prepare-chart))
643 ((message-name= "road_section_cache" message)
644 (digest-road-section-raw-data)
645 (cache-images))
646 ((message-name= "road_section_cncl" message)
647 (restore-road-section-selection)
648 (setf *caching-images-p* nil)
649 (pipeglade-out "road_section" "set_visible" 0))
650 ((message-name= "road_network" message)
651 (collect-raw-message message *road-network-chart-raw-data*))
652 ((message-name= "zeb" message)
653 (collect-raw-message message *zeb-chart-raw-data*))
654 ((message-name= "render_accidents" message)
655 (setf (first *accidents-chart-raw-data*) (message-info message)))
656 ((message-name= "accidents_from" message)
657 (setf (second *accidents-chart-raw-data*) (message-data message)))
658 ((message-name= "accidents_to" message)
659 (setf (third *accidents-chart-raw-data*) (message-data message)))
660 ((message-name= "chart_configuration_ok" message)
661 (setf *road-network-chart-configuration* (digest-chart-raw-data *road-network-chart-raw-data*))
662 (save-place *road-network-chart-configuration* 'road-network-chart-configuration)
663 (setf *zeb-chart-configuration* (digest-chart-raw-data *zeb-chart-raw-data*))
664 (save-place *zeb-chart-configuration* 'zeb-chart-configuration)
665 (digest-accidents-chart-raw-data)
666 (update-accidents-chart-dialog)
667 (pipeglade-out "text_values" "clear")
668 (prepare-chart))
669 ((message-name= "chart_configuration_cncl" message)
670 (update-accidents-chart-dialog)
671 (setf *accidents-chart-raw-data* (list nil nil nil))
672 (pipeglade-out "chart_configuration" "set_visible" 0))
673 ((message-name= "credentials_check" message)
674 (check-credentials-dialog-statuses))
675 ((message-name= "credentials_ok" message)
676 (check-credentials-dialog-statuses)
677 (when (db-credentials-modifiedp *postgresql-road-network-credentials*)
678 (invalidate-road-section-selection)
679 (invalidate-road-section)
680 (invalidate-road-network-chart-configuration)
681 (populate-road-section-dialog)
682 (update-chart-dialog)
683 (save-road-network-credentials nil))
684 (when (db-credentials-modifiedp *postgresql-zeb-credentials*)
685 (update-chart-dialog)
686 (invalidate-zeb-chart-configuration)
687 (pipeglade-out "text_values" "clear")
688 (prepare-chart)
689 (save-zeb-credentials nil))
690 (when (db-credentials-modifiedp *postgresql-accidents-credentials*)
691 (prepare-chart)
692 (save-accidents-credentials nil))
693 (handler-case (apply #'phoros-login *phoros-url* *phoros-credentials*)
694 (phoros-server-error ()))
695 (cancel-launch-image)
696 (update-station (saved-station))
697 (update-chart-dialog))
698 ((message-name= "road_network_host" message)
699 (setf (db-credentials-host *postgresql-road-network-credentials*) (message-data message))
700 (save-road-network-credentials t))
701 ((message-name= "road_network_port" message)
702 (setf (db-credentials-port *postgresql-road-network-credentials*)
703 (parse-integer (message-data message) :junk-allowed t))
704 (save-road-network-credentials t))
705 ((message-name= "road_network_ssl" message)
706 (setf (db-credentials-ssl *postgresql-road-network-credentials*) (if (string= (message-data message) "1") :yes :no))
707 (save-road-network-credentials t))
708 ((message-name= "road_network_database" message)
709 (setf (db-credentials-database *postgresql-road-network-credentials*) (message-data message))
710 (save-road-network-credentials t))
711 ((message-name= "road_network_user" message)
712 (setf (db-credentials-user *postgresql-road-network-credentials*) (message-data message))
713 (save-road-network-credentials t))
714 ((message-name= "road_network_password" message)
715 (setf (db-credentials-password *postgresql-road-network-credentials*) (message-data message))
716 (save-road-network-credentials t))
717 ((message-name= "road_network_table" message)
718 (setf (db-credentials-table *postgresql-road-network-credentials*) (message-data message))
719 (save-road-network-credentials t))
720 ((message-name= "zeb_host" message)
721 (setf (db-credentials-host *postgresql-zeb-credentials*) (message-data message))
722 (save-zeb-credentials t))
723 ((message-name= "zeb_port" message)
724 (setf (db-credentials-port *postgresql-zeb-credentials*)
725 (parse-integer (message-data message) :junk-allowed t))
726 (save-zeb-credentials t))
727 ((message-name= "zeb_ssl" message)
728 (setf (db-credentials-ssl *postgresql-zeb-credentials*) (if (string= (message-info message) "1") :yes :no))
729 (save-zeb-credentials t))
730 ((message-name= "zeb_database" message)
731 (setf (db-credentials-database *postgresql-zeb-credentials*) (message-data message))
732 (save-zeb-credentials t))
733 ((message-name= "zeb_user" message)
734 (setf (db-credentials-user *postgresql-zeb-credentials*) (message-data message))
735 (save-zeb-credentials t))
736 ((message-name= "zeb_password" message)
737 (setf (db-credentials-password *postgresql-zeb-credentials*) (message-data message))
738 (save-zeb-credentials t))
739 ((message-name= "zeb_table" message)
740 (setf (db-credentials-table *postgresql-zeb-credentials*) (message-data message))
741 (save-zeb-credentials t))
742 ((message-name= "accidents_host" message)
743 (setf (db-credentials-host *postgresql-accidents-credentials*) (message-data message))
744 (save-accidents-credentials t))
745 ((message-name= "accidents_port" message)
746 (setf (db-credentials-port *postgresql-accidents-credentials*)
747 (parse-integer (message-data message) :junk-allowed t))
748 (save-accidents-credentials t))
749 ((message-name= "accidents_ssl" message)
750 (setf (db-credentials-ssl *postgresql-accidents-credentials*) (if (string= (message-data message) "1") :yes :no))
751 (save-accidents-credentials t))
752 ((message-name= "accidents_database" message)
753 (setf (db-credentials-database *postgresql-accidents-credentials*) (message-data message))
754 (save-accidents-credentials t))
755 ((message-name= "accidents_user" message)
756 (setf (db-credentials-user *postgresql-accidents-credentials*) (message-data message))
757 (save-accidents-credentials t))
758 ((message-name= "accidents_password" message)
759 (setf (db-credentials-password *postgresql-accidents-credentials*) (message-data message))
760 (save-accidents-credentials t))
761 ((message-name= "accidents_table" message)
762 (setf (db-credentials-table *postgresql-accidents-credentials*) (message-data message))
763 (save-accidents-credentials t))
764 ((message-name= "phoros_url" message)
765 (setf *phoros-url* (message-data message))
766 (save-phoros-credentials))
767 ((message-name= "phoros_user" message)
768 (setf (first *phoros-credentials*) (message-data message))
769 (save-phoros-credentials))
770 ((message-name= "phoros_password" message)
771 (setf (second *phoros-credentials*) (message-data message))
772 (save-phoros-credentials))
773 ((message-name= "phoros" message)
774 (run-phoros-browser))
776 (print (list "fallen through:" message)))))))
777 (sb-sys:interactive-interrupt () (kill-pipeglade))
778 (usocket:ns-condition (c)
779 (format *error-output* "DNS error: ~A~%" c)
780 (kill-pipeglade))
781 (error (e)
782 (print e)
783 (kill-pipeglade))
786 (defun kill-pipeglade ()
787 (let ((pipeglade-pid
788 (with-open-file (stream *pipeglade-pid-file* :direction :input)
789 (read stream nil))))
790 (uiop:run-program (format nil "kill ~A" pipeglade-pid))))
792 (defun invalidate-road-section ()
793 (setf *road-section* nil)
794 (save-road-section))
796 (defun invalidate-road-section-selection ()
797 (setf *road-section-selection* '())
798 (save-road-section-selection))
800 (defun invalidate-road-network-chart-configuration ()
801 (setf *road-network-chart-configuration* nil)
802 (save-place *road-network-chart-configuration* 'road-network-chart-configuration))
804 (defun invalidate-zeb-chart-configuration ()
805 (setf *zeb-chart-configuration* nil)
806 (save-place *zeb-chart-configuration* 'zeb-chart-configuration))
808 (defun message-name= (string message)
809 (let ((colon-position (position #\: message)))
810 (string= string (subseq message 0 colon-position))))
812 (defun message-info (message)
813 (let ((colon-position (position #\: message))
814 (space-position (position #\Space message)))
815 (subseq message (1+ colon-position) space-position)))
817 (defun message-data (message)
818 (let ((space-position (position #\Space message)))
819 (when space-position
820 (subseq message (1+ space-position)))))
822 (defun message-data-list (message)
823 (cl-utilities:split-sequence #\Space (message-data message)))
825 (defun collect-road-section-select (message)
826 (let ((data (message-data-list message)))
827 (if (string= (second data) "4") ;"select" column
828 (setf (gethash (parse-integer (first data)) ;row number
829 *road-section-raw-data*)
830 (string= (third data) "1")))))
832 ;; (defun collect-accidents-message-data (&key (renderp 0 renderp-p) (from nil from-p) (to nil to-p) (ok-pressed nil ok-pressed-p))
833 ;; (when renderp-p (setf (first *accidents-chart-raw-data*) renderp))
834 ;; (when from-p (setf (second *accidents-chart-raw-data*) (parse-integer from :junk-allowed t)))
835 ;; (when to-p (setf (third *accidents-chart-raw-data*) (parse-integer to :junk-allowed t))))
837 (defun collect-raw-message (message place)
838 (unless (string= (message-info message) "clicked")
839 (let ((data (message-data-list message)))
840 (setf (gethash (list (parse-integer (first data)) ;row number
841 (parse-integer (second data))) ;column number
842 place)
843 (third data)))))
845 (defun digest-road-section-raw-data ()
846 (when (and *postgresql-road-network-credentials* *postgresql-road-network-ok*)
847 (let* ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*))))
848 (sections-current (position (cdr *road-section*) sections :test #'equal)))
849 (maphash (lambda (key value)
850 (if value
851 (pushnew key *road-section-selection*)
852 (setf *road-section-selection* (remove key *road-section-selection*))))
853 *road-section-raw-data*)
854 (setf *road-section-selection* (sort *road-section-selection* #'<))
855 (save-road-section-selection)
856 (unless (find sections-current *road-section-selection*)
857 (set-road-section :direction :first)
858 (save-road-section)
859 (update-station 0))
860 (clrhash *road-section-raw-data*))))
862 (defstruct (data-style (:type list)) chartp drawablep textp name color width dash)
864 ;; (defun clear-main-window ()
865 ;; (dolist (drawingarea '("chart_accidents" "chart_road_network" "chart_zeb" "chart_cursor" "chart_road_network_scale" "chart_zeb_scale" "draw_rearview" "draw_frontview"))
866 ;; (pipeglade-out drawingarea "remove" 2))
867 ;; (dolist (image '("img_rearview" "img_frontview"))
868 ;; (pipeglade-out image "set_from_file"))
869 ;; (pipeglade-out "text_values" "clear"))
871 (defun digest-chart-raw-data (raw-data)
872 "Return the information read from raw-data in chart configuration format."
873 (let* ((row-count
874 (loop
875 for (row column) being each hash-key of raw-data
876 when (zerop column) ;arbitrary column representing its row
877 count it))
878 (chart-configuration
879 (make-array (list row-count))))
880 (loop
881 for i from 0 below row-count
883 (setf (svref chart-configuration i)
884 (make-data-style)))
885 (loop
886 for (row column) being each hash-key of raw-data using (hash-value value)
888 (case column
889 (0 ;column name
890 (setf (data-style-name (svref chart-configuration row)) value))
891 ;; 1 would be type
892 (2 ;width
893 (setf (data-style-width (svref chart-configuration row)) value))
894 (3 ;color
895 (setf (data-style-color (svref chart-configuration row)) value))
896 (4 ;dash
897 (setf (data-style-dash (svref chart-configuration row)) value))
898 (5 ;selected
899 (setf (data-style-chartp (svref chart-configuration row)) (string= value "1")))
901 (setf (data-style-textp (svref chart-configuration row)) (string= value "1")))
903 (setf (data-style-drawablep (svref chart-configuration row)) (string= value "1")))))
904 chart-configuration))
906 (defun digest-accidents-chart-raw-data ()
907 (setf *accidents-chart-configuration*
908 (mapcar (lambda (configuration-value raw-value)
909 (or (format nil "~D"
910 (handler-case
911 (parse-integer raw-value :junk-allowed t)
912 (type-error ()))
913 configuration-value)))
914 *accidents-chart-configuration*
915 *accidents-chart-raw-data*))
916 (save-accidents-chart-configuration))
918 (defun road-network-chart-data (column vnk nnk chart-height)
919 "Return a list of lists of station and column values between vnk
920 and nnk scaled into chart-height; the minimum column value; and the
921 maximum column value. Both minimum and maximum are nil if data is
922 constant."
923 (let ((table (intern (db-credentials-table *postgresql-road-network-credentials*))))
924 (with-connection *postgresql-road-network-credentials*
925 (setf column (intern (string-upcase column)))
926 (destructuring-bind (minimum maximum)
927 (query (:select (:type (:min column) real)
928 (:type (:max column) real)
929 :from table
930 :where (:and (:= 'vnk vnk)
931 (:= 'nnk nnk)))
932 :list)
933 (if (and (numberp minimum) (numberp maximum))
934 (let* ((span (- maximum minimum))
935 (m (if (zerop span)
937 (/ chart-height span)))
938 (b (if (zerop span)
939 (* chart-height 1/2)
940 (+ chart-height (* m minimum)))))
941 (values
942 (query (:order-by
943 (:select 'nk-station
944 (:- b (:* m (:type column real)))
945 :from table
946 :where (:and (:= 'vnk vnk)
947 (:= 'nnk nnk)))
948 'nk-station))
949 minimum
950 maximum ))
951 (values nil nil nil))))))
953 (defun zeb-chart-data (column vnk nnk chart-height)
954 "Return a list of lists of station and column values between vnk
955 and nnk scaled into chart-height; the minimum column value; and the
956 maximum column value. Both minimum and maximum are nil if data is
957 constant."
958 (let ((table (intern (db-credentials-table *postgresql-zeb-credentials*))))
959 (with-connection *postgresql-zeb-credentials*
960 (setf column (intern (string-upcase column)))
961 (destructuring-bind (minimum maximum)
962 (query (:select (:type (:min column) real)
963 (:type (:max column) real)
964 :from table
965 :where (:and (:= 'vnk vnk)
966 (:= 'nnk nnk)))
967 :list)
968 (if (and (numberp minimum) (numberp maximum))
969 (let* ((span (- maximum minimum))
970 (m (if (zerop span)
972 (/ chart-height span)))
973 (b (if (zerop span)
974 (* chart-height 1/2)
975 (+ chart-height (* m minimum)))))
976 (values
977 (query (:order-by
978 (:select 'vst
979 (:- b (:* m (:type column real)))
980 'bst
981 (:- b (:* m (:type column real)))
982 :from table
983 :where (:and (:= 'vnk vnk)
984 (:= 'nnk nnk)))
985 'vst))
986 minimum
987 maximum))
988 (values nil nil nil))))))
992 (defun road-network-text-value (column vnk nnk station)
993 "Return column value at station between vnk and nnk."
994 (when *postgresql-road-network-ok*
995 (let ((table (intern (db-credentials-table *postgresql-road-network-credentials*))))
996 (with-connection *postgresql-road-network-credentials*
997 (setf column (intern (string-upcase column)))
998 (query (:select column
999 :from table
1000 :where (:and (:= 'vnk vnk)
1001 (:= 'nnk nnk)
1002 (:= 'nk_station station)))
1003 :single)))))
1005 (defun zeb-text-value (column vnk nnk station)
1006 "Return column value at station between vnk and nnk."
1007 (when *postgresql-zeb-ok*
1008 (let ((table (intern (db-credentials-table *postgresql-zeb-credentials*))))
1009 (with-connection *postgresql-zeb-credentials*
1010 (setf column (intern (string-upcase column)))
1011 (query (:select column
1012 :from table
1013 :where (:and (:= 'vnk vnk)
1014 (:= 'nnk nnk)
1015 (:between station 'vst 'bst)))
1016 :single)))))
1018 (defun show-text (row-number station text-data-function column vnk nnk color width dash)
1019 (let ((value (funcall text-data-function column vnk nnk station)))
1020 (pipeglade-out "text_values" "set" row-number 0 column)
1021 (pipeglade-out "text_values" "set" row-number 1 value)
1022 (pipeglade-out "text_values" "set" row-number 2 color)
1023 (pipeglade-out "text_values" "set" row-number 3 (* 4 (parse-integer width :junk-allowed t))))) ;text size
1025 (defun put-text-values (vnk nnk station)
1026 (let ((row-number 0))
1027 (when (vectorp *road-network-chart-configuration*)
1028 (loop
1029 for style-definition across *road-network-chart-configuration*
1031 (when (data-style-textp style-definition)
1032 (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))
1033 (incf row-number))))
1034 (when (vectorp *zeb-chart-configuration*)
1035 (loop
1036 for style-definition across *zeb-chart-configuration*
1038 (when (data-style-textp style-definition)
1039 (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))
1040 (incf row-number))))))
1042 (defun accidents-data (vnk nnk &key
1043 (year-min most-negative-fixnum)
1044 (year-max most-positive-fixnum))
1045 "Return a list of plists containing accident data for the road
1046 section between vnk and nnk."
1047 (when *postgresql-accidents-ok*
1048 (let ((table (intern (db-credentials-table *postgresql-accidents-credentials*))))
1049 (with-connection *postgresql-accidents-credentials*
1050 (query (:order-by
1051 (:select 'nk-station 'fahrtrichtung 'unfalltyp 'unfallkategorie 'alkohol
1052 :from table
1053 :where (:and (:= 'vnk vnk)
1054 (:= 'nnk nnk)
1055 (:between 'jahr year-min year-max)))
1056 'nk-station 'jahr 'monat 'tag 'stunde 'minuten)
1057 :plists)))))
1059 (defun populate-road-section-dialog ()
1060 (when *postgresql-road-network-ok*
1061 (with-statusbar-message "populating road section list"
1062 (with-spinner "road_section_spinner"
1063 (pipeglade-out "road_sections" "clear")
1064 (with-connection *postgresql-road-network-credentials*
1065 (let ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))))
1066 (loop
1067 for (vnk nnk length) in sections
1068 for row-number from 0
1070 (add-vnk-nnk-leaf vnk nnk length row-number))))))))
1072 (defun update-credentials-dialog ()
1073 (with-statusbar-message "initialising credentials"
1074 (pipeglade-out "road_network_host" "set_text" (db-credentials-host *postgresql-road-network-credentials*))
1075 (pipeglade-out "road_network_port" "set_text" (db-credentials-port *postgresql-road-network-credentials*))
1076 (pipeglade-out "road_network_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-road-network-credentials*) :no) 0 1))
1077 (pipeglade-out "road_network_database" "set_text" (db-credentials-database *postgresql-road-network-credentials*))
1078 (pipeglade-out "road_network_table" "set_text" (db-credentials-table *postgresql-road-network-credentials*))
1079 (pipeglade-out "road_network_user" "set_text" (db-credentials-user *postgresql-road-network-credentials*))
1080 (pipeglade-out "road_network_password" "set_text" (db-credentials-password *postgresql-road-network-credentials*))
1081 (pipeglade-out "road_network_status" "set_text" "?")
1082 (pipeglade-out "zeb_host" "set_text" (db-credentials-host *postgresql-zeb-credentials*))
1083 (pipeglade-out "zeb_port" "set_text" (db-credentials-port *postgresql-zeb-credentials*))
1084 (pipeglade-out "zeb_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-zeb-credentials*) :no) 0 1))
1085 (pipeglade-out "zeb_database" "set_text" (db-credentials-database *postgresql-zeb-credentials*))
1086 (pipeglade-out "zeb_table" "set_text" (db-credentials-table *postgresql-zeb-credentials*))
1087 (pipeglade-out "zeb_user" "set_text" (db-credentials-user *postgresql-zeb-credentials*))
1088 (pipeglade-out "zeb_password" "set_text" (db-credentials-password *postgresql-zeb-credentials*))
1089 (pipeglade-out "zeb_status" "set_text" "?")
1090 (pipeglade-out "accidents_host" "set_text" (db-credentials-host *postgresql-accidents-credentials*))
1091 (pipeglade-out "accidents_port" "set_text" (db-credentials-port *postgresql-accidents-credentials*))
1092 (pipeglade-out "accidents_ssl" "set_active" (if (eq (db-credentials-ssl *postgresql-accidents-credentials*) :no) 0 1))
1093 (pipeglade-out "accidents_database" "set_text" (db-credentials-database *postgresql-accidents-credentials*))
1094 (pipeglade-out "accidents_table" "set_text" (db-credentials-table *postgresql-accidents-credentials*))
1095 (pipeglade-out "accidents_user" "set_text" (db-credentials-user *postgresql-accidents-credentials*))
1096 (pipeglade-out "accidents_password" "set_text" (db-credentials-password *postgresql-accidents-credentials*))
1097 (pipeglade-out "accidents_status" "set_text" "?")
1098 (when *phoros-credentials*
1099 (destructuring-bind (user password) *phoros-credentials*
1100 (pipeglade-out "phoros_url" "set_text" *phoros-url*)
1101 (pipeglade-out "phoros_user" "set_text" user)
1102 (pipeglade-out "phoros_password" "set_text" password)
1103 (pipeglade-out "phoros_status" "set_text" "?")))))
1105 (defun check-credentials-dialog-statuses ()
1106 (with-statusbar-message "checking road network db connection"
1107 (multiple-value-bind (message successp) (check-db *postgresql-road-network-credentials*)
1108 (pipeglade-out "road_network_status" "set_text" message)
1109 (setf *postgresql-road-network-ok* successp)))
1110 (with-statusbar-message "checking zeb db connection"
1111 (multiple-value-bind (message successp) (check-db *postgresql-zeb-credentials*)
1112 (pipeglade-out "zeb_status" "set_text" message)
1113 (setf *postgresql-zeb-ok* successp)))
1114 (with-statusbar-message "checking accidents db connection"
1115 (multiple-value-bind (message successp) (check-db *postgresql-accidents-credentials*)
1116 (pipeglade-out "accidents_status" "set_text" message)
1117 (setf *postgresql-accidents-ok* successp)))
1118 (with-statusbar-message "checking Phoros connection"
1119 (pipeglade-out "phoros_status" "set_text" (and *phoros-url*
1120 *phoros-credentials*
1121 (apply #'check-phoros *phoros-url* *phoros-credentials*)))))
1123 (defun save-place (place filename-stump)
1124 "Save place into a file whose name is based on symbol filename-stump."
1125 (let ((cache-file-name (cache-file-name filename-stump)))
1126 (ensure-directories-exist cache-file-name)
1127 (with-open-file (stream cache-file-name
1128 :direction :output
1129 :if-exists :supersede)
1130 (prin1 place stream))))
1132 (defmacro restore-place (place filename-stump &optional default)
1133 "Restore place from a file whose name is based on symbol filename-stump."
1134 (cl-utilities:with-unique-names (stream)
1135 `(with-open-file (stream (cache-file-name ,filename-stump)
1136 :direction :input
1137 :if-does-not-exist nil)
1138 (if stream
1139 (setf ,place (read stream))
1140 (setf ,place ,default)))))
1142 (defun save-road-section-selection ()
1143 "Save the list of road sections selected for processing."
1144 (save-place *road-section-selection* 'road-section-selection))
1146 (defun restore-road-section-selection ()
1147 (restore-place *road-section-selection* 'road-section-selection))
1149 (defun update-road-section-selection ()
1150 (when *postgresql-road-network-ok*
1151 (with-statusbar-message "restoring road section selection"
1152 (with-spinner "road_section_spinner"
1153 (with-connection *postgresql-road-network-credentials*
1154 (let ((sections (sections (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))))
1155 (loop
1156 for row-number from 0 below (length sections)
1158 (if (find row-number *road-section-selection*)
1159 (pipeglade-out "road_sections" "set" row-number 4 1)
1160 (pipeglade-out "road_sections" "set" row-number 4 0))))))
1161 (pipeglade-out "road_sections" "scroll"
1162 (or (ignore-errors (apply #'min *road-section-selection*))
1164 0))))
1166 (defun restore-road-section-image-counts ()
1167 (when *postgresql-road-network-ok*
1168 (with-statusbar-message "restoring road section image counts"
1169 (with-connection *postgresql-road-network-credentials*
1170 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1171 (sections (sections table)))
1172 (loop
1173 for (vnk nnk) in sections
1174 for row-number from 0
1175 do (multiple-value-bind (rearview-image-data rearview-cached-p)
1176 (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 t :from-cache-only t)
1177 (multiple-value-bind (frontview-image-data frontview-cached-p)
1178 (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 nil :from-cache-only t)
1179 (when (and rearview-cached-p frontview-cached-p)
1180 (pipeglade-out "road_sections" "set" row-number 3 (+ (length rearview-image-data) (length frontview-image-data))))))))))))
1182 (defun save-road-network-credentials (modifiedp)
1183 (setf (db-credentials-modifiedp *postgresql-road-network-credentials*) modifiedp)
1184 (save-place *postgresql-road-network-credentials* 'road-network-credentials))
1186 (defun restore-road-network-credentials ()
1187 (restore-place *postgresql-road-network-credentials* 'road-network-credentials *postgresql-road-network-credentials*))
1189 (defun save-zeb-credentials (modifiedp)
1190 (setf (db-credentials-modifiedp *postgresql-zeb-credentials*) modifiedp)
1191 (save-place *postgresql-zeb-credentials* 'zeb-credentials))
1193 (defun restore-zeb-credentials ()
1194 (restore-place *postgresql-zeb-credentials* 'zeb-credentials *postgresql-zeb-credentials*))
1196 (defun save-accidents-credentials (modifiedp)
1197 (setf (db-credentials-modifiedp *postgresql-accidents-credentials*) modifiedp)
1198 (save-place *postgresql-accidents-credentials* 'accidents-credentials))
1200 (defun restore-accidents-credentials ()
1201 (restore-place *postgresql-accidents-credentials* 'accidents-credentials *postgresql-accidents-credentials*))
1203 (defun save-phoros-credentials ()
1204 (save-place *phoros-credentials* 'phoros-credentials)
1205 (save-place *phoros-url* 'phoros-url))
1207 (defun restore-phoros-credentials ()
1208 (restore-place *phoros-credentials* 'phoros-credentials *phoros-credentials*)
1209 (restore-place *phoros-url* 'phoros-url *phoros-url*))
1211 (defun save-road-section ()
1212 "Save road-section into cache directory."
1213 (save-place *road-section* 'road-section))
1215 (defun restore-road-section ()
1216 (restore-place *road-section* 'road-section))
1218 (defun save-accidents-chart-configuration ()
1219 (save-place *accidents-chart-configuration* 'accidents-chart-configuration))
1221 (defun saved-station ()
1222 (let ((cache-file-name (cache-file-name 'station))
1223 station)
1224 (ensure-directories-exist cache-file-name)
1225 (with-open-file (stream cache-file-name
1226 :direction :input
1227 :if-does-not-exist nil)
1228 (when stream (setf station (read stream)))
1229 (or station 0))))
1231 (defun restore-road-network-chart-configuration ()
1232 (unless (db-credentials-modifiedp *postgresql-road-network-credentials*)
1233 (restore-place *road-network-chart-configuration* 'road-network-chart-configuration)))
1235 (defun restore-zeb-chart-configuration ()
1236 (unless (db-credentials-modifiedp *postgresql-zeb-credentials*)
1237 (restore-place *zeb-chart-configuration* 'zeb-chart-configuration)))
1239 (defun restore-accidents-chart-configuration ()
1240 (unless (db-credentials-modifiedp *postgresql-accidents-credentials*)
1241 (restore-place *accidents-chart-configuration* 'accidents-chart-configuration (list "1" "1999" "2030"))))
1243 (defun set-road-section (&key direction)
1244 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1245 (sections (sections table))
1246 (sections-current (position (cdr *road-section*) sections :test #'equal))
1247 (selection-current (position sections-current *road-section-selection*)))
1248 (cond ((not *road-section-selection*)
1249 (invalidate-road-section))
1250 ((eq direction :predecessor)
1251 (let ((selection-predecessor (ignore-errors (nth (1- selection-current) *road-section-selection*))))
1252 (when selection-predecessor
1253 (setf *road-section*
1254 (cons table (nth selection-predecessor sections)))
1255 (save-road-section))))
1256 ((eq direction :successor)
1257 (let* ((selection-successor (nth (1+ selection-current) *road-section-selection*)))
1258 (when selection-successor
1259 (setf *road-section*
1260 (cons table (nth selection-successor sections)))
1261 (save-road-section))))
1262 ((eq direction :last)
1263 (setf *road-section* (cons table
1264 (nth (car (last *road-section-selection*)) sections)))
1265 (save-road-section))
1266 ((eq direction :first)
1267 (setf *road-section* (cons table
1268 (nth (first *road-section-selection*) sections)))
1269 (save-road-section))
1271 (error "impossible road section")))))
1273 (defun update-station (station)
1274 (when (numberp station)
1275 (pipeglade-out "station_scale" "set_value" station)))
1277 (defun jump-to-station-worker ()
1278 (let ((current-station)
1279 (current-road-section))
1280 (loop
1281 (cond ((not *road-section*)
1282 (sleep .2)
1283 (bt:thread-yield))
1284 ((and (eql current-station *station*)
1285 (equal current-road-section *road-section*))
1286 (sleep .1)
1287 (bt:thread-yield))
1289 (psetf current-station *station*
1290 current-road-section *road-section*)
1291 (handler-case
1292 (destructuring-bind (table vnk nnk road-section-length)
1293 current-road-section
1294 (pipeglade-out "station" "set_text" current-station)
1295 (place-chart-cursor current-station)
1296 (put-image :vnk vnk :nnk nnk :station current-station :step 10 :rear-view-p t)
1297 (put-image :vnk vnk :nnk nnk :station current-station :step 10 :rear-view-p nil)
1298 (put-text-values vnk nnk current-station))
1299 (database-connection-error ())
1300 (database-error ())))))))
1302 (defun check-db (db-credentials &aux result)
1303 "Check database connection and presence of table or view table-name.
1304 Return a string describing the outcome."
1305 (let ((table-name (db-credentials-table db-credentials)))
1306 (handler-case
1307 (trivial-timeout:with-timeout (3)
1308 (with-connection db-credentials
1309 (if (or (table-exists-p table-name)
1310 (view-exists-p table-name))
1311 (setf result (list "ok" t))
1312 (setf result (list "table or view missing" nil)))))
1313 (database-connection-error (e) (setf result (list e nil)))
1314 (cl+ssl:ssl-error-verify (e) (setf result (list e nil)))
1315 (sb-bsd-sockets:name-service-error (e) (setf result (list e nil)))
1316 (database-error (e) (setf result (list e nil)))
1317 (trivial-timeout:timeout-error () (setf result (list "timeout" nil))))
1318 (values-list result)))
1320 (defun check-phoros (url user-name password)
1321 "Check connection to phoros server. Return a string describing the
1322 outcome."
1323 (let ((*phoros-url* url)
1324 (*phoros-cookies* nil))
1325 (unwind-protect
1326 (handler-case (phoros-login url user-name password)
1327 (usocket:ns-host-not-found-error () "host not found")
1328 (usocket:connection-refused-error () "connection refused")
1329 (error (c) (format nil "~A" c))
1330 (:no-error (result) (if result "ok" "wrong user or password")))
1331 (phoros-logout))))
1333 (defun populate-chart-dialog ()
1334 (with-statusbar-message "initialising chart configuration"
1335 (when *postgresql-road-network-ok*
1336 (update-chart-dialog-treeview "road_network" *postgresql-road-network-credentials* *road-network-chart-configuration*))
1337 (when *postgresql-zeb-ok*
1338 (update-chart-dialog-treeview "zeb" *postgresql-zeb-credentials* *zeb-chart-configuration*))
1339 (when *postgresql-accidents-ok*
1340 (update-accidents-chart-dialog))))
1342 (defun update-chart-dialog ()
1343 (with-statusbar-message "updating chart configuration"
1344 (when (and (db-credentials-modifiedp *postgresql-road-network-credentials*)
1345 *postgresql-road-network-ok*)
1346 (update-chart-dialog-treeview "road_network" *postgresql-road-network-credentials* *road-network-chart-configuration*)
1347 (save-road-network-credentials nil))
1348 (when (and (db-credentials-modifiedp *postgresql-zeb-credentials*)
1349 *postgresql-zeb-ok*)
1350 (update-chart-dialog-treeview "zeb" *postgresql-zeb-credentials* *zeb-chart-configuration*)
1351 (save-zeb-credentials nil))
1352 (when (and (db-credentials-modifiedp *postgresql-accidents-credentials*)
1353 *postgresql-accidents-ok*)
1354 (update-accidents-chart-dialog)
1355 (save-accidents-credentials nil))))
1357 (defun update-chart-dialog-treeview (treeview db-credentials chart-configuration)
1358 (with-statusbar-message "updating treeview configuration"
1359 (handler-case
1360 (with-connection db-credentials
1361 (present-db-columns (table-description (db-credentials-table db-credentials)) treeview chart-configuration))
1362 (database-connection-error ()))))
1364 (defun update-accidents-chart-dialog ()
1365 (pipeglade-out "render_accidents" "set_active" (first *accidents-chart-configuration*))
1366 (pipeglade-out "accidents_from" "set_text" (second *accidents-chart-configuration*))
1367 (pipeglade-out "accidents_to" "set_text" (third *accidents-chart-configuration*)))
1369 (defun present-db-columns (columns treeview chart-configuration)
1370 (pipeglade-out treeview "clear")
1371 (loop
1372 for (column-name type) in (sort columns #'string-lessp :key #'car)
1373 for row-number from 0
1375 (let ((selected-column (find column-name chart-configuration :key #'data-style-name :test #'string-equal))
1376 (drawablep (numeric-type-p type)))
1377 (pipeglade-out treeview "set" row-number 0 column-name)
1378 (pipeglade-out treeview "set" row-number 1 type)
1379 (pipeglade-out treeview "set" row-number 2 (or (data-style-width selected-column) 2))
1380 (pipeglade-out treeview "set" row-number 3 (or (data-style-color selected-column) "black"))
1381 (pipeglade-out treeview "set" row-number 4 (or (data-style-dash selected-column) ""))
1382 (pipeglade-out treeview "set" row-number 5 (if (and drawablep (data-style-chartp selected-column)) 1 0))
1383 (pipeglade-out treeview "set" row-number 6 (if (data-style-textp selected-column) 1 0))
1384 (pipeglade-out treeview "set" row-number 7 (if drawablep 1 0))
1385 (pipeglade-out treeview "set_cursor" row-number))) ;tickle initial pipeglade output
1386 (pipeglade-out treeview "set_cursor")
1387 (pipeglade-out treeview "scroll" 0 0))
1389 (defun numeric-type-p (type)
1390 (some #'identity (mapcar (lambda (x) (search x type))
1391 '("float" "double" "int" "numeric" "serial"))))
1393 (defun add-vnk-nnk-leaf (vnk nnk length row-number)
1394 "Put a leaf into road-sections tree."
1395 (pipeglade-out "road_sections" "set" row-number 0 vnk)
1396 (pipeglade-out "road_sections" "set" row-number 1 nnk)
1397 (pipeglade-out "road_sections" "set" row-number 2 length))
1399 (defun prepare-chart ()
1400 "Prepare chart for the road section between vnk and nnk in table in
1401 current database."
1402 (if *road-section*
1403 (destructuring-bind (table vnk nnk road-section-length) *road-section*
1404 (pipeglade-out "ovl_chart" "set_size_request" (+ *chart-tail* road-section-length) (+ *chart-height* *chart-fringe*))
1405 (pipeglade-out "vnk" "set_text" vnk)
1406 (pipeglade-out "nnk" "set_text" nnk)
1407 (pipeglade-out "length" "set_text" road-section-length)
1408 (draw-chart-cursor-scale road-section-length)
1409 (pipeglade-out "station_scale" "set_range" 0 road-section-length)
1410 (draw-graphs vnk nnk)
1411 ;; (update-station (saved-station))
1413 (progn
1414 (clear-date-image-and-arrow "rear_view_time" "img_rearview" "draw_rearview")
1415 (clear-date-image-and-arrow "front_view_time" "img_frontview" "draw_frontview")
1416 (pipeglade-out "vnk" "set_text")
1417 (pipeglade-out "nnk" "set_text")
1418 (pipeglade-out "length" "set_text")
1419 ;; (update-station 0)
1420 (pipeglade-out "station" "set_text"))))
1422 (defun place-chart-cursor (station)
1423 "Move chart cursor to station."
1424 (when station
1425 (pipeglade-out "chart_cursor" "remove" 2)
1426 (pipeglade-out "chart_cursor" "move_to" 2 station 0)
1427 (pipeglade-out "chart_cursor" "line_to" 2 station (+ *chart-height* *chart-fringe*))
1428 (pipeglade-out "chart_cursor" "stroke" 2)
1429 (pipeglade-out "chart_scroll" "hscroll_to_range" (- station 200) (+ station 200))
1430 (pipeglade-out "chart_road_network_scale" "translate" "=3" station 0)
1431 (pipeglade-out "chart_zeb_scale" "translate" "=3" station 0)))
1433 (defun draw-graphs (vnk nnk)
1434 "Draw graphs for the columns in *zeb-chart-configuration* and
1435 *road-network-chart-configuration*. Delete existing graphs first."
1436 (with-statusbar-message "drawing chart"
1437 (with-spinner "chart_spinner"
1438 (pipeglade-out "chart_road_network" "remove" 2)
1439 (pipeglade-out "chart_road_network_scale" "remove" 2)
1440 (pipeglade-out "chart_road_network_scale" "translate" "=3" 0 0)
1441 (pipeglade-out "chart_zeb" "remove" 2)
1442 (pipeglade-out "chart_zeb_scale" "remove" 2)
1443 (pipeglade-out "chart_zeb_scale" "translate" "=3" 0 0)
1444 (let ((scale-position *scale-distance*))
1445 (with-statusbar-message "drawing road-network chart"
1446 (when (vectorp *road-network-chart-configuration*)
1447 (loop
1448 for style-definition across *road-network-chart-configuration*
1450 (when (data-style-chartp style-definition)
1451 (handler-case
1452 (progn
1453 (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))
1454 (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))
1455 (incf scale-position *scale-distance*))
1456 (database-error (e) (format t "(draw-graphs), road-network: ~A~%" e)))))))
1457 (with-statusbar-message "drawing zeb chart"
1458 (when (vectorp *zeb-chart-configuration*)
1459 (loop
1460 for style-definition across *zeb-chart-configuration*
1462 (when (data-style-chartp style-definition)
1463 (handler-case
1464 (progn
1465 (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))
1466 (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))
1467 (incf scale-position *scale-distance*))
1468 (database-error (e) (format t "(draw-graphs), zeb: ~A~%" e))))))))
1469 (pipeglade-out "chart_accidents" "remove" 2)
1470 (handler-case
1471 (progn
1472 (draw-accidents vnk nnk))
1473 (database-error (e) (format t "(draw-graphs), accidents: ~A~%" e))))))
1475 (defun draw-graph (chart-data-function chart column vnk nnk color width dash)
1476 (multiple-value-bind (line minimum maximum)
1477 (funcall chart-data-function column vnk nnk *chart-height*)
1478 (let ((line-fragments
1479 (cl-utilities:split-sequence-if #'(lambda (x)
1480 (eq (second x) :null))
1481 line
1482 :remove-empty-subseqs t)))
1483 (pipeglade-out chart "set_source_rgba" 2 color)
1484 (pipeglade-out chart "set_line_width" 2 width)
1485 (pipeglade-out chart "set_dash" 2 dash)
1486 (dolist (line-fragment line-fragments)
1487 (pipeglade-out chart "move_to" 2 (first (car line-fragment)) (second (car line-fragment)))
1488 (dolist (line-vertex (cdr line-fragment))
1489 (pipeglade-out chart "line_to" 2 (first line-vertex) (second line-vertex)))
1490 (pipeglade-out chart "stroke" 2)))))
1492 (defun draw-scale (position chart-data-function chart column vnk nnk color width dash)
1493 (multiple-value-bind (line minimum maximum)
1494 (funcall chart-data-function column vnk nnk *chart-height*)
1495 (pipeglade-out chart "set_source_rgba" 2 color)
1496 (pipeglade-out chart "set_line_width" 2 width)
1497 (pipeglade-out chart "move_to" 2 position 0)
1498 (pipeglade-out chart "line_to" 2 position *chart-height*)
1499 (dolist (tick (axis-ticks minimum maximum 5 *chart-height* t))
1500 (pipeglade-out chart "move_to" 2 position (format nil "~F" (second tick)))
1501 (pipeglade-out chart "rel_line_to" 2 (* 2 (parse-integer width)) 0)
1502 (pipeglade-out chart "move_to" 2 position (format nil "~F" (second tick)))
1503 (pipeglade-out chart "rel_move_to" 2 (- (parse-integer width)) 0)
1504 (pipeglade-out chart "rel_move_for" 2 "e" (first tick))
1505 (pipeglade-out chart "show_text" 2 (first tick)))
1506 (pipeglade-out chart "move_to" 2 position (format nil "~F" (+ *chart-height* (/ *chart-fringe* 2))))
1507 (pipeglade-out chart "rel_move_for" 2 "c" column)
1508 (pipeglade-out chart "show_text" 2 column)
1509 (pipeglade-out chart "stroke" 2)))
1511 (defun draw-chart-cursor-scale (length)
1512 (let ((y-position (+ *chart-height* *chart-fringe*))
1513 (number-of-ticks (round length 100)))
1514 (pipeglade-out "chart_cursor" "remove" 4)
1515 (dolist (tick (axis-ticks 0 length number-of-ticks length nil))
1516 (pipeglade-out "chart_cursor" "move_to" 4 (second tick) y-position)
1517 (pipeglade-out "chart_cursor" "line_to" 4 (second tick) (- y-position 3))
1518 (pipeglade-out "chart_cursor" "rel_move_for" 4 "s" (first tick))
1519 (pipeglade-out "chart_cursor" "show_text" 4 (first tick)))
1520 (pipeglade-out "chart_cursor" "stroke" 4)))
1522 (defun axis-ticks (minimum maximum n chart-size reversep)
1523 (let ((range (- maximum minimum)))
1524 (if (zerop range)
1525 (list (list (format nil "~F" minimum) (/ chart-size 2)))
1526 (let* ((a (if reversep
1527 (- (/ chart-size range))
1528 (/ chart-size range)))
1529 (b (if reversep
1530 (* a maximum)
1531 (- (* a maximum) chart-size)))
1532 (min-step (/ range (1+ n)))
1533 (max-step (/ range (1- n)))
1534 (max-exp (log max-step 10))
1535 (int-exp (floor max-exp))
1536 (norm-min (floor (/ min-step (expt 10 int-exp))))
1537 (norm-max (floor (/ max-step (expt 10 int-exp))))
1538 (norm (cond
1539 ((or (= norm-min 1) (= norm-max 1))
1541 ((or (<= norm-min 4 norm-max) (<= norm-min 5 norm-max) (<= norm-min 6 norm-max))
1543 ((or (<= norm-min 2) (<= norm-min 3 norm-max))
1544 2.5)
1545 ((<= 7 norm-max)
1548 norm-max))) ;can't happen
1549 (step (* norm (expt 10 int-exp)))
1550 (start (- minimum (nth-value 1 (fceiling minimum step)))))
1551 (loop
1552 for i from start to maximum by step
1553 collect (list (if (minusp int-exp)
1554 (format nil "~,VF" (- int-exp) i)
1555 (format nil "~A" (round i)))
1556 (- (* a i) b)))))))
1558 (defun draw-accidents (vnk nnk)
1559 (when (string-equal (first *accidents-chart-configuration*) "1")
1560 (let* ((year-min (second *accidents-chart-configuration*))
1561 (year-max (third *accidents-chart-configuration*))
1562 (accidents (accidents-data vnk nnk :year-min year-min :year-max year-max))
1563 (current-station -1)
1564 (zeroth-position -1)
1565 y1-position
1566 y2-position)
1567 (dolist (accident accidents)
1568 (unless (= current-station (getf accident :nk-station))
1569 (setf y1-position (- *chart-height* zeroth-position))
1570 (setf y2-position zeroth-position)
1571 (setf y0-position (+ (/ *chart-height* 2) zeroth-position)))
1572 (setf current-station (getf accident :nk-station))
1573 (cond ((= 1 (getf accident :fahrtrichtung))
1574 (draw-accident accident (decf y1-position 10)))
1575 ((= 2 (getf accident :fahrtrichtung))
1576 (draw-accident accident (incf y2-position 10)))
1578 (draw-accident accident (incf y0-position 10))))))))
1580 (defun draw-accident (accident y-position)
1581 "Put graphical representation of accident on chart."
1582 (destructuring-bind (&key nk-station fahrtrichtung unfalltyp unfallkategorie alkohol)
1583 accident
1584 (when (and (numberp alkohol) (plusp alkohol)) (draw-triangle nk-station y-position "lightblue"))
1585 (case unfallkategorie
1586 (1 (draw-rectangle nk-station y-position 10 "black")
1587 (draw-circle nk-station y-position 8 (accident-type-color unfalltyp)))
1588 (2 (draw-circle nk-station y-position 8 (accident-type-color unfalltyp)))
1589 (3 (draw-circle nk-station y-position 6 (accident-type-color unfalltyp)))
1590 (4 (draw-circle nk-station y-position 6 "white")
1591 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1592 (5 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1593 (6 (draw-triangle nk-station y-position "lightblue")
1594 (draw-circle nk-station y-position 4 (accident-type-color unfalltyp)))
1595 (t (draw-circle nk-station y-position 4 (accident-type-color unfalltyp))))))
1597 (defun draw-circle (x y diameter color)
1598 (pipeglade-out "chart_accidents" "set_source_rgba" 2 "black")
1599 (pipeglade-out "chart_accidents" "arc" 2 x y (/ diameter 2) 0 360)
1600 (pipeglade-out "chart_accidents" "stroke_preserve" 2)
1601 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1602 (pipeglade-out "chart_accidents" "fill" 2))
1604 (defun draw-rectangle (x y diameter color)
1605 (let ((radius (/ diameter 2)))
1606 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1607 (pipeglade-out "chart_accidents" "rectangle" 2 (- x radius) (- y radius) diameter diameter)
1608 (pipeglade-out "chart_accidents" "fill" 2)))
1610 (defun draw-triangle (x y color)
1611 (pipeglade-out "chart_accidents" "set_source_rgba" 2 "black")
1612 (pipeglade-out "chart_accidents" "move_to" 2 (- x 3) (- y 6))
1613 (pipeglade-out "chart_accidents" "line_to" 2 (+ x 3) (- y 6))
1614 (pipeglade-out "chart_accidents" "line_to" 2 x (+ y 9))
1615 (pipeglade-out "chart_accidents" "close_path" 2)
1616 (pipeglade-out "chart_accidents" "stroke_preserve" 2)
1617 (pipeglade-out "chart_accidents" "set_source_rgba" 2 color)
1618 (pipeglade-out "chart_accidents" "fill" 2))
1620 (defun accident-type-color (accident-type)
1621 (case accident-type
1622 (1 "green")
1623 (2 "yellow")
1624 (3 "red")
1625 (4 "white")
1626 (5 "lightblue")
1627 (6 "orange")
1628 (7 "black")
1629 (t "darkblue")))
1631 (defun iso-time (time)
1632 (when time
1633 (multiple-value-bind (seconds deciseconds)
1634 (floor time)
1635 (multiple-value-bind (second minute hour date month year day daylight-p zone)
1636 (decode-universal-time seconds)
1637 (format nil "~D-~2,'0D-~2,'0D\\n~2,'0D:~2,'0D:~2,'0D~3,3FZ" year month date hour minute second deciseconds)))))
1639 (defun image-point-coordinates (image-data-alist global-point-coordinates)
1640 "Return a list (m n) of image coordinates representing
1641 global-point-coordinates in the image described in image-data-alist
1642 but scaled to fit into *image-size*."
1643 (handler-case
1644 (convert-image-coordinates
1645 (photogrammetry :reprojection
1646 image-data-alist
1647 (pairlis '(:x-global :y-global :z-global)
1648 (proj:cs2cs
1649 (list
1650 (proj:degrees-to-radians
1651 (coordinates-longitude global-point-coordinates))
1652 (proj:degrees-to-radians
1653 (coordinates-latitude global-point-coordinates))
1654 (coordinates-ellipsoid-height global-point-coordinates))
1655 :destination-cs (cdr (assoc :cartesian-system image-data-alist)))))
1656 image-data-alist)
1657 (error (e) nil)))
1659 ;; (defun in-image-p (m n)
1660 ;; "Check if m, n lay inside *image-size*."
1661 ;; (and m n (<= 0 m (first *image-size*)) (<= 0 n (second *image-size*))))
1663 (defun-cached sections (table)
1664 "Return list of distinct pairs of vnk, nnk found in table in
1665 current database."
1666 (query (:order-by (:select 'vnk 'nnk (:max 'nk-station)
1667 :from (intern (db-credentials-table *postgresql-road-network-credentials*))
1668 :where (:and (:not-null 'vnk) (:not-null 'nnk))
1669 :group-by 'vnk 'nnk)
1670 'vnk 'nnk)))
1672 (defun stations (table vnk nnk &optional (step 1))
1673 "Return a list of plists of :longitude, :latitude,
1674 :ellipsoid-height, :station, :azimuth of stations step metres apart
1675 between vnk and nnk."
1676 (when (and table vnk nnk)
1677 (let ((stations
1678 (query
1679 (:order-by
1680 (:select (:as (:st_x 't1.the-geom) 'longitude)
1681 (:as (:st_y 't1.the-geom) 'latitude)
1682 (:as (:st_z 't1.the-geom) 'ellipsoid-height)
1683 (:as 't1.nk-station 'station)
1684 (:as (:st_azimuth 't1.the-geom 't2.the-geom) 'azimuth)
1685 :from (:as table 't1)
1686 :left-join (:as table 't2)
1687 :on (:and (:= 't1.nk-station (:- 't2.nk-station 1))
1688 (:= 't2.vnk vnk)
1689 (:= 't2.nnk nnk))
1690 :where (:and (:= 't1.vnk vnk)
1691 (:= 't1.nnk nnk)
1692 (:= 0 (:% 't1.nk-station step))))
1693 't1.nk-station)
1694 :plists)))
1695 (setf
1696 (getf (nth (- (length stations) 1) stations) :azimuth)
1697 (getf (nth (- (length stations) 2) stations) :azimuth))
1698 stations)))
1700 (defun-cached all-stations (table vnk nnk)
1701 "Return a vector of coordinates of all points between vnk and nnk,
1702 station (in metres) being the vector index."
1703 (when (and table vnk nnk)
1704 (let* ((stations (stations table vnk nnk))
1705 (result (make-array (list (1+ (getf (first (last stations)) :station)))
1706 :initial-element nil)))
1707 (loop
1708 for i in stations
1709 do (destructuring-bind (&key longitude latitude ellipsoid-height station azimuth)
1711 (setf (svref result station)
1712 (make-coordinates :longitude longitude
1713 :latitude latitude
1714 :ellipsoid-height ellipsoid-height
1715 :azimuth azimuth))))
1716 result)))
1718 (defun-cached road-section-image-data (provenience-string table vnk nnk step rear-view-p)
1719 "Return a list of instances of image data corresponding to stations,
1720 which are step metres apart, found in table in current database.
1721 provenience-string only serves as a marker of the provenience of image
1722 data once cached."
1723 (remove nil ;; (mapcar #'(lambda (x)
1724 ;; (apply #'image-data :rear-view-p rear-view-p x))
1725 ;; (stations table vnk nnk step))
1726 (loop
1727 with azimuth-fallback = nil
1728 for station in (stations table vnk nnk step)
1729 when (not (eq (getf station :azimuth) :null))
1730 do (setf azimuth-fallback (getf station :azimuth))
1731 and collect (apply #'image-data :rear-view-p rear-view-p station)
1733 when (and azimuth-fallback
1734 (eq (getf station :azimuth) :null))
1735 do (setf (getf station :azimuth) azimuth-fallback)
1736 and collect (apply #'image-data :rear-view-p rear-view-p station))))
1738 (defun provenience-string (url)
1739 "Turn url recognisably into something suitable as part of a file
1740 name."
1741 (let ((parsed-url (puri:parse-uri url)))
1742 (format nil "~A_~A~{_~A~}"
1743 (puri:uri-host parsed-url)
1744 (puri:uri-port parsed-url)
1745 (cl-utilities:split-sequence
1746 #\/ (puri:uri-path parsed-url) :remove-empty-subseqs t))))
1748 (defun cache-file-name (kind &rest args)
1749 "Return pathname for a cache file distinguishable by kind and args."
1750 (make-pathname :directory *cache-dir*
1751 :name (format nil "~{~:[f~;~:*~(~A~)~]_~}~S.~S"
1752 args
1753 (fasttrack-version :major t)
1754 (fasttrack-version :minor t))
1755 :type (string-downcase kind)))
1757 (defun cache-images ()
1758 "Download images of road-sections selected in dialog into their
1759 canonical places."
1760 (unless *caching-images-p*
1761 (setf *caching-images-p* t)
1762 (bt:make-thread
1763 (lambda ()
1764 (when *postgresql-road-network-ok*
1765 (with-statusbar-message "caching images"
1766 (with-spinner "road_section_spinner"
1767 (handler-bind
1768 ((phoros-server-error (lambda (e) (invoke-restart 'retry))))
1769 (with-connection *postgresql-road-network-credentials*
1770 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
1771 (sections (sections table)))
1772 (loop
1773 for selected-section in *road-section-selection*
1775 (cache-road-section-images (nth selected-section sections) table))))))))
1776 (setf *caching-images-p* nil))
1777 :name "cache-images")))
1779 (let ((retry-delay 1))
1780 (defun cache-road-section-images (section table)
1781 (destructuring-bind (vnk nnk length)
1782 section
1783 (restart-case
1784 (progn
1785 (loop
1786 for image-data in (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 t)
1787 do (if *caching-images-p*
1788 (download-image image-data)
1789 (loop-finish)))
1790 (loop
1791 for image-data in (road-section-image-data (provenience-string *phoros-url*) table vnk nnk 10 nil)
1792 do (if *caching-images-p*
1793 (download-image image-data)
1794 (loop-finish)))
1795 (setf retry-delay 1))
1796 (retry ()
1797 (with-statusbar-message (format nil "error while caching images; retry after ~A seconds" retry-delay)
1798 (sleep retry-delay))
1799 (when (< retry-delay 15)
1800 (incf retry-delay 1))
1801 (cache-road-section-images section table))))))
1803 (defun get-image-data (road-section-image-data station step)
1804 "Return image data for the image near station."
1805 (or (find (* step (round station step)) road-section-image-data
1806 :key #'image-data-station
1807 :test #'=)
1808 *empty-image-data*))
1810 (defun get-image-data-alist (road-section-image-data station step)
1811 "Return as an alist data for the image near station."
1812 (image-data-alist (get-image-data road-section-image-data station step)))
1814 (defun image-data (&key longitude latitude ellipsoid-height station azimuth rear-view-p)
1815 "Get from Phoros server image data for location near longitude,
1816 latitude."
1817 (handler-case
1818 (let* ((coordinates (make-coordinates :longitude longitude
1819 :latitude latitude
1820 :ellipsoid-height ellipsoid-height
1821 :azimuth azimuth))
1822 (image-data (phoros-nearest-image-data coordinates rear-view-p)))
1823 (when (image-data-p image-data)
1824 (setf (image-data-station image-data) station)
1825 (setf (image-data-station-coordinates image-data) coordinates)
1826 image-data))
1827 (phoros-server-error (e) (format t "(image-data): ~A" e))))
1829 (define-condition phoros-server-error (error)
1830 ((body :reader body :initarg :body)
1831 (status-code :reader status-code :initarg :status-code)
1832 (headers :reader headers :initarg :headers)
1833 (url :reader url :initarg :url)
1834 (reason-phrase :reader reason-phrase :initarg :reason-phrase))
1835 (:report (lambda (condition stream)
1836 (format stream "Can't connect to Phoros server: ~A (~D)"
1837 (reason-phrase condition) (status-code condition)))))
1839 (defun phoros-lib-url (canonical-url suffix)
1840 "Replace last path element of canonical-url by lib/<suffix>."
1841 (when canonical-url
1842 (let* ((parsed-canonical-url (puri:parse-uri canonical-url))
1843 (old-path (puri:uri-parsed-path parsed-canonical-url))
1844 (new-path (append (butlast old-path) (list "lib" suffix)))
1845 (new-url (puri:copy-uri parsed-canonical-url)))
1846 (setf (puri:uri-parsed-path new-url) new-path)
1847 new-url)))
1849 (defun phoros-login (url user-name user-password)
1850 "Log into Phoros server; return T if successful. Try logging out
1851 first."
1852 ;; (setf *phoros-url* url)
1853 (setf drakma:*allow-dotless-cookie-domains-p* t)
1854 (pushnew (cons "application" "json") drakma:*text-content-types* :test #'equal)
1855 (phoros-logout)
1856 (setf *phoros-cookies* (make-instance 'drakma:cookie-jar))
1857 (when url
1858 (multiple-value-bind (body status-code headers url stream must-close reason-phrase)
1859 (drakma:http-request (puri:parse-uri url) :cookie-jar *phoros-cookies*)
1860 (declare (ignore stream must-close))
1861 (assert (= status-code 200) ()
1862 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1863 (multiple-value-bind (body status-code headers authenticate-url stream must-close reason-phrase)
1864 (drakma:http-request (phoros-lib-url url "authenticate")
1865 :cookie-jar *phoros-cookies*
1866 :form-data t
1867 :method :post
1868 :parameters (pairlis '("user-name" "user-password")
1869 (list user-name user-password)))
1870 (declare (ignore stream must-close))
1871 (assert (< status-code 400) ()
1872 'phoros-server-error :body body :status-code status-code :headers headers :url authenticate-url :reason-phrase reason-phrase)
1873 (let ((body-strings (cl-utilities:split-sequence #\Space (substitute-if-not #\Space #'alphanumericp body))))
1874 (and (not (find "Rejected" body-strings :test #'string=))
1875 (not (find "Retry" body-strings :test #'string=))
1876 (= status-code 200))))))) ;should be 302 (?)
1878 (defun phoros-logout ()
1879 (drakma:http-request (phoros-lib-url *phoros-url* "logout")))
1881 (defun run-phoros-browser ()
1882 (when *road-section*
1883 (with-statusbar-message "calling browser synchronously"
1884 (destructuring-bind (table vnk nnk road-section-length)
1885 *road-section*
1886 (let ((current-coordinates (svref (all-stations table vnk nnk) (saved-station))))
1887 (handler-case
1888 (uiop:run-program (format nil "firefox '~A/lib/set-cursor?bbox=~F,~F,~F,~F&longitude=~F&latitude=~F'"
1889 *phoros-url*
1890 (- (coordinates-longitude current-coordinates) .02)
1891 (- (coordinates-latitude current-coordinates) .01)
1892 (+ (coordinates-longitude current-coordinates) .02)
1893 (+ (coordinates-latitude current-coordinates) .01)
1894 (coordinates-longitude current-coordinates)
1895 (coordinates-latitude current-coordinates)
1897 (type-error () nil))
1898 (uiop:run-program (format nil "firefox '~A'" *phoros-url*)))))))
1900 (defun heading (azimuth rear-view-p)
1901 "Return as a string the one of east, west, north, south which best
1902 describes azimuth."
1903 (cond ((<= (* 1/4 pi) azimuth (* 3/4 pi)) (if rear-view-p "west" "east"))
1904 ((<= (* 3/4 pi) azimuth (* 5/4 pi)) (if rear-view-p "north" "south"))
1905 ((<= (* 5/4 pi) azimuth (* 7/4 pi)) (if rear-view-p "east" "west"))
1906 ((or (<= (* 5/4 pi) azimuth pi) (<= 0 (* 1/4 pi))) (if rear-view-p "south" "north"))))
1908 (defun phoros-nearest-image-data (coordinates rear-view-p)
1909 "Return a set of image-data."
1910 (handler-case
1911 (multiple-value-bind (body status-code headers url stream must-close reason-phrase)
1912 (drakma:http-request (phoros-lib-url *phoros-url* "nearest-image-data")
1913 :cookie-jar *phoros-cookies*
1914 :method :post
1915 :content-type "text/plain; charset=UTF-8"
1916 :content (json:encode-json-plist-to-string (list :longitude (coordinates-longitude coordinates)
1917 :latitude (coordinates-latitude coordinates)
1918 :zoom 20
1919 :count 1
1920 :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
1921 (declare (ignore stream must-close))
1922 (assert (= status-code 200) ()
1923 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1924 (unless (string-equal body "null")
1925 (apply #'make-image-data :allow-other-keys t
1926 (plist-from-alist
1927 (car (json:decode-json-from-string body))))))
1928 (usocket:ns-error (e) (format *error-output* "nearest-image-data: ~A~%" e))))
1930 (defun download-file (url path)
1931 "Unless already there, store content from url under path. Return
1932 nil if nothing needed storing."
1933 (when path
1934 (ensure-directories-exist path)
1935 (with-open-file (file-stream path :direction :output
1936 :element-type 'unsigned-byte
1937 :if-exists nil)
1938 (when file-stream
1939 (with-statusbar-message (format nil "downloading ~A" url)
1940 (multiple-value-bind
1941 (body status-code headers url stream must-close reason-phrase)
1942 (drakma:http-request url
1943 :cookie-jar *phoros-cookies*
1944 :method :get)
1945 (declare (ignore stream must-close))
1946 (setf *t* url)
1947 (assert (= status-code 200) ()
1948 'phoros-server-error :body body :status-code status-code :headers headers :url url :reason-phrase reason-phrase)
1949 (write-sequence body file-stream)
1950 reason-phrase))))))
1952 (defun download-image (image-data)
1953 "If not already there, download a png image, shrink it, convert it
1954 into jpg, and store it under the cache path. Return that path."
1955 (multiple-value-bind (url origin-path destination-path)
1956 (image-url image-data)
1957 (when destination-path
1958 (unless (probe-file destination-path)
1959 (download-file url origin-path)
1960 (apply #'convert-image-file origin-path destination-path *image-size*)
1961 (delete-file origin-path))
1962 destination-path)))
1964 (defun launch-image (image-data image-arrow-coordinates rear-view-p)
1965 (if rear-view-p
1966 (if *show-rear-view-p*
1967 (progn
1968 (psetf *rear-view-image-data* image-data
1969 *rear-view-image-arrow-coordinates* image-arrow-coordinates))
1970 (progn
1971 (setf *rear-view-image-data* *empty-image-data*)
1972 (pipeglade-out "draw_rearview" "remove" 2)
1973 (pipeglade-out "img_rearview" "set_from_file" "public_html/phoros-logo-background.png")))
1974 (if *show-front-view-p*
1975 (progn
1976 (psetf *front-view-image-data* image-data
1977 *front-view-image-arrow-coordinates* image-arrow-coordinates))
1978 (progn
1979 (setf *front-view-image-data* *empty-image-data*)
1980 (pipeglade-out "draw_frontview" "remove" 2)
1981 (pipeglade-out "img_frontview" "set_from_file" "public_html/phoros-logo-background.png")))))
1983 (defun cancel-launch-image ()
1984 (setf *rear-view-image-data* *empty-image-data*)
1985 (setf *front-view-image-data* *empty-image-data*))
1987 (defun image-data-alist (image-data)
1988 "Return an alist representation of image-data."
1989 (when image-data
1990 (loop
1991 for i in (append (mapcar #'ensure-hyphen-before-digit *aggregate-view-columns*) '(station station-coordinates))
1992 collect (intern (string i) 'keyword) into keys
1993 collect (funcall (intern (concatenate 'string (string 'image-data-)
1994 (string i)))
1995 image-data)
1996 into values
1997 finally (return (pairlis keys values)))))
1999 (defun plist-from-alist (alist)
2000 (loop
2001 for (key . value) in alist
2002 collect key
2003 collect value))
2005 (defun image-url (image-data)
2006 "Return an image URL made from ingredients found in image-data, the
2007 corresponding cache path, and the corresponding cache path for the
2008 shrunk image."
2009 (when image-data
2010 (let* ((path
2011 (format nil "~A/~A/~A/~D.png"
2012 (puri:uri-path (phoros-lib-url *phoros-url* "photo"))
2013 (image-data-directory image-data)
2014 (image-data-filename image-data)
2015 (image-data-byte-position image-data)))
2016 (query
2017 (format nil "mounting-angle=~D~
2018 &bayer-pattern=~{~D~#^,~}~
2019 &color-raiser=~{~D~#^,~}"
2020 (image-data-mounting-angle image-data)
2021 (map 'list #'identity (image-data-bayer-pattern image-data))
2022 (map 'list #'identity (image-data-color-raiser image-data))))
2023 (url (puri:copy-uri (puri:parse-uri *phoros-url*) :path path :query query))
2024 (host (puri:uri-host url))
2025 (port (puri:uri-port url))
2026 (cache-directory (append *cache-dir*
2027 (list (format nil "~A_~D" host port))
2028 (cdr (pathname-directory (puri:uri-path url)))))
2029 (cache-name (pathname-name (puri:uri-path url)))
2030 (cache-type (pathname-type (puri:uri-path url))))
2031 (values url
2032 (make-pathname :directory cache-directory
2033 :name cache-name
2034 :type cache-type)
2035 (make-pathname :directory cache-directory
2036 :name cache-name
2037 :type "jpg")))))
2039 (defun convert-image-file (origin-file destination-file width height)
2040 "Convert origin-file into destination-file of a maximum size of
2041 width x height."
2042 (uiop:run-program
2043 (format nil "convert ~A -scale ~Dx~D ~A" origin-file width height destination-file)))
2045 (defun convert-image-coordinates (original-coordinates-alist image-data-alist)
2046 "Convert image coordinates from original-coordinates-alist for the
2047 image in image-data-alist into a list of coordinates for that image
2048 scaled and centered to *image-size*."
2049 (let* ((original-m (cdr (assoc :m original-coordinates-alist)))
2050 (original-n (cdr (assoc :n original-coordinates-alist)))
2051 (original-width (cdr (assoc :sensor-width-pix image-data-alist)))
2052 (original-height (cdr (assoc :sensor-height-pix image-data-alist)))
2053 (new-width (first *image-size*))
2054 (new-height (second *image-size*))
2055 (scaling-factor (min (/ new-width original-width) (/ new-height original-height)))
2056 (new-m-offset (/ (- new-width (* original-width scaling-factor)) 2))
2057 (new-n-offset (/ (- new-height (* original-height scaling-factor)) 2))
2058 (new-m (+ (* original-m scaling-factor) new-m-offset))
2059 (new-n (- new-height ;flip n
2060 (+ (* original-n scaling-factor) new-n-offset))))
2061 (mapcar #'round (list new-m new-n))))
2063 (defun put-image (&key vnk nnk station step rear-view-p)
2064 "Put an image along with a labelled station marker on screen."
2065 (when (and vnk nnk station)
2066 (with-connection *postgresql-road-network-credentials*
2067 (setf station (or station 0))
2068 (let* ((table (make-symbol (db-credentials-table *postgresql-road-network-credentials*)))
2069 (point-radius 5)
2070 (image-widget (if rear-view-p "img_rearview" "img_frontview"))
2071 (drawing-widget (if rear-view-p "draw_rearview" "draw_frontview"))
2072 (spinner-widget (if rear-view-p "spinner_rearview" "spinner_frontview"))
2073 (time-widget (if rear-view-p "rear_view_time" "front_view_time"))
2074 global-point-coordinates
2075 image-data-alist
2076 image-data
2077 image-arrow-coordinates
2078 global-point-coordinates-thread)
2079 (setf global-point-coordinates-thread
2080 (bt:make-thread
2081 (lambda ()
2082 (with-connection *postgresql-road-network-credentials*
2083 (setf global-point-coordinates
2084 (subseq (all-stations table vnk nnk :message (list vnk nnk))
2085 (min (length (all-stations table vnk nnk)) station)
2086 (min (length (all-stations table vnk nnk)) (+ station 4))))))
2087 :name "global-point-coordinates"))
2088 (bt:join-thread global-point-coordinates-thread)
2089 (setf image-data-alist
2090 (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")))
2091 station
2092 step))
2093 (setf image-arrow-coordinates
2094 (loop
2095 for i across global-point-coordinates
2096 append (image-point-coordinates image-data-alist i)))
2097 (setf image-label-coordinates (ignore-errors
2098 (list (- (first image-arrow-coordinates) point-radius)
2099 (- (second image-arrow-coordinates) point-radius))))
2100 (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))
2101 (launch-image image-data image-arrow-coordinates rear-view-p)))))
2103 (defun cruise-control (&key backwardp)
2104 (setf *cruise-control-backward-p* backwardp)
2105 (setf *cruise-control* t)) ;picked up by cruise-control-worker
2107 (defun stop-cruise-control ()
2108 (setf *cruise-control* nil))
2110 (defun cruise-control-worker ()
2111 (loop
2112 (let ((road-section-length (fourth *road-section*)))
2113 (if (and *cruise-control*
2114 *rear-view-image-done*
2115 *front-view-image-done*)
2116 (progn
2117 (let ((next-station
2118 (+ *station* (if *cruise-control-backward-p*
2119 (- *big-step*)
2120 *big-step*))))
2121 (when (< next-station 0)
2122 (setf next-station 0)
2123 (stop-cruise-control))
2124 (when (> next-station road-section-length)
2125 (setf next-station road-section-length)
2126 (stop-cruise-control))
2127 (setf *rear-view-image-done* nil)
2128 (setf *front-view-image-done* nil)
2129 (sleep .2)
2130 (update-station next-station)))
2131 (progn
2132 (sleep .2)
2133 (bt:thread-yield))))))