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