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