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