root/trunk/projects/bos/m2/map.lisp

Revision 3942, 13.8 kB (checked in by hans, 2 months ago)

Merge from anon-transaction-fixes-2 branch. This changeset removes
make-object and initialize-persistent-instance, makes the allocation
of object IDs simpler and more safe and removes several relicts from
previous refactoring iterations. Also, the store tests have been
extended significantly to test pathological cases and create objects
from multiple threads.

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 (in-package :bos.m2)
2
3 ;;;; ORIGINAL-MAP-TILE
4
5 (define-persistent-class original-map-tile ()
6   ((x :read)
7    (y :read)
8    (image :update :relaxed-object-reference t))
9   (:default-initargs :image nil)
10   (:class-indices (tile-index :index-type array-index
11                               :slots (x y)
12                               :index-reader original-map-tile-at
13                               :index-initargs (:dimensions (list (/ +width+ +m2tile-width+) (/ +width+ +m2tile-width+))))))
14
15 (defmethod print-object ((object original-map-tile) stream)
16   (print-unreadable-object (object stream :type t :identity nil)
17     (ignore-errors
18       (with-slots (x y image) object
19         (format stream "at (~D,~D) ~:[(no image)~;~A~]"
20                 (* x +m2tile-width+)
21                 (* y +m2tile-width+)
22                 image)))))
23
24 (defun get-original-map-tile (x y)
25   (original-map-tile-at (list (floor x +m2tile-width+) (floor y +m2tile-width+))))
26
27 (defun get-original-image-at (x y)
28   (let ((tile (get-original-map-tile x y)))
29     (and tile
30          (original-map-tile-image tile))))
31
32 (defun ensure-original-map-tile (x y)
33   (or (get-original-map-tile x y)
34       (make-instance 'original-map-tile
35                      :x (floor x +m2tile-width+)
36                      :y (floor y +m2tile-width+))))
37
38 ;;;; IMAGE-TILE
39 ;;;;
40 ;;;; Kachel mit Kartenausschnitt - Jede Kachel (tile) enthÀlt ein
41 ;;;; Image mit dem Ausschnitt aus dem Satellitenbild im Format 90x90
42 ;;;; Pixel.  Das Image ist im Slot 'original-image im Originalzustand
43 ;;;; abgelegt.  Im Slot 'current-images ist eine Hashtable mit Images
44 ;;;; in verschiedenen Renderings abgelegt.  Der Key der Hashtable ist
45 ;;;; der Typ des Derivats (nil => Originalbild, contracts => Originalbild
46 ;;;; mit kolorierten verkauften Gebieten etc).
47
48 ;;;; image-tiles verwalten die Erzeugung von Kacheln mit
49 ;;;; eingezeichneten VertrÀgen.  Die VertrÀge werden dabei farblich
50 ;;;; unterschiedlich markiert.  Die markierte Version der Kachel wird
51 ;;;; als transienter Slot mitgefÃŒhrt, d.h. bei einem Neustart des
52 ;;;; Systems werden die Kacheln beim Zugriff neu berechnet.
53
54 ;;;; Die Implementation der Bildverarbeitungsroutinen erfolgt dabei
55 ;;;; mit einer primitiven Image-Klasse, die ein Truecolor-Bild als
56 ;;;; 2D-Array von 32 bit langen RGBA-Werten behandelt.  Dies ist
57 ;;;; notwendig, da cl-gd den Zugriff auf einzelne Pixelwerte derzeit
58 ;;;; nur ÃŒber das FFI erlaubt, was extrem langsam ist.  Es gibt eine
59 ;;;; alternative API, mit der man schnell ÃŒber cl-gd-images iterieren
60 ;;;; kann, sie ist jedoch makrobasiert und nicht flexibel - Sie
61 ;;;; erlaubt ausschließlich das Iterieren ÃŒber ein Image.
62
63 (defun colorize-pixel (pixel-rgb-value color-red color-green color-blue)
64   "Colorize the given PIXEL-RGB-VALUE in the COLOR given.
65 PIXEL-RGB-VALUE is a raw truecolor pixel with RGB components.  COLOR
66 is a truecolor PIXEL with the raw color to assign to the pixel.
67 PIXEL-RGB-VALUE is converted to grayscale, the grayscale level is used
68 to determine the intensity of the returned RGB value."
69   (declare (fixnum pixel-rgb-value))
70   (let* ((red (ldb (byte 8 16) pixel-rgb-value))
71          (green (ldb (byte 8 8) pixel-rgb-value))
72          (blue (ldb (byte 8 0) pixel-rgb-value))
73          (level (/ (+ (* 0.3 red) (* 0.59 green) (* 0.11 blue)) 255)))
74     (setq red (floor (* color-red level))
75           green (floor (* color-green level))
76           blue (floor (* color-blue level)))
77     (setf (ldb (byte 8 16) pixel-rgb-value) red)
78     (setf (ldb (byte 8 8) pixel-rgb-value) green)
79     (setf (ldb (byte 8 0) pixel-rgb-value) blue)
80     pixel-rgb-value))
81
82 ;;; allocation-area-inclusion-cache
83 (defstruct (allocation-area-inclusion-cache (:conc-name ac-))
84   x y width height array areas)
85
86 (defvar *allocation-area-inclusion-cache* nil
87   "allocation-area-inclusion-cache struct indicating whether a certain square meter is inside of an allocation area")
88
89 (defun point-in-any-allocation-area-p% (x-coord y-coord)
90   (find-if #'(lambda (allocation-area)
91                ;; first check whether point is in bounding box, then do full polygon check
92                (and (point-in-polygon-p x-coord y-coord (allocation-area-bounding-box allocation-area))
93                     (point-in-polygon-p x-coord y-coord (allocation-area-vertices allocation-area))))
94            (store-objects-with-class 'allocation-area)))
95
96 (defun initialize-allocation-area-inclusion-cache ()
97   (destructuring-bind (x y width height) (allocation-areas-bounding-box)
98     (setf *allocation-area-inclusion-cache*
99           (make-allocation-area-inclusion-cache :x x :y y :width width :height height
100                                                 :array (make-array (list width height) :element-type '(unsigned-byte 1))
101                                                 :areas (class-instances 'allocation-area))))
102   (dolist (area (ac-areas *allocation-area-inclusion-cache*))
103     (destructuring-bind (top-left-x top-left-y width height) (allocation-area-bounding-box2 area)
104       (dotimes (x width)
105         (dotimes (y height)
106           (let ((x-coord (+ x top-left-x))
107                 (y-coord (+ y top-left-y)))
108             (when (and (point-in-polygon-p x-coord y-coord (allocation-area-bounding-box area))
109                        (point-in-polygon-p x-coord y-coord (allocation-area-vertices area)))
110               (setf (aref (ac-array *allocation-area-inclusion-cache*)
111                           (- x-coord (ac-x *allocation-area-inclusion-cache*))
112                           (- y-coord (ac-y *allocation-area-inclusion-cache*)))
113                     1))))))))
114
115 (defvar *allocation-area-inclusion-cache-lock* (bt:make-lock "Area Cache Lock"))
116
117 (defun validate-allocation-area-inclusion-cache ()
118   (bt:with-lock-held (*allocation-area-inclusion-cache-lock*)
119     (unless (and *allocation-area-inclusion-cache*
120                  (equal (class-instances 'allocation-area)
121                         (ac-areas *allocation-area-inclusion-cache*)))
122       (initialize-allocation-area-inclusion-cache))))
123
124 (defun point-in-any-allocation-area-p (x-coord y-coord)
125   (and (< -1 (- x-coord (ac-x *allocation-area-inclusion-cache*)) (ac-width *allocation-area-inclusion-cache*))
126        (< -1 (- y-coord (ac-y *allocation-area-inclusion-cache*)) (ac-height *allocation-area-inclusion-cache*))
127        (plusp (aref (ac-array *allocation-area-inclusion-cache*)
128                     (- x-coord (ac-x *allocation-area-inclusion-cache*))
129                     (- y-coord (ac-y *allocation-area-inclusion-cache*))))))
130
131 (defclass image-tile (tile)
132   ((original-image :documentation "Original satellite image"
133                    :initform nil)
134    (changed-time :initarg :changed-time
135                  :accessor image-tile-changed-time
136                  :documentation "Timestamp of last change in contracts pointing to this tile")
137    (layers :initarg :layers :reader image-tile-layers))
138   (:default-initargs :type :png :changed-time (get-universal-time) :layers '(background areas contracts palette)))
139
140 (defmethod initialize-instance :after ((tile image-tile) &key nw-x nw-y width &allow-other-keys)
141   (register-rect-subscriber *rect-publisher* tile
142                             (list nw-x nw-y width width) #'image-tile-changed))
143
144 (defmethod image-tile-original-image ((tile image-tile))
145   (with-slots (original-image nw-x nw-y) tile
146     (unless original-image
147       (setf original-image (get-original-image-at nw-x nw-y)))
148     original-image))
149
150 (defmethod image-tile-process ((tile image-tile) (operation (eql :background)))
151   (when (image-tile-original-image tile)
152     (with-store-image (original-image (image-tile-original-image tile))
153       (copy-image original-image *default-image* 0 0 0 0 (image-width) (image-height)))))
154
155 (defmethod image-tile-process ((tile image-tile) (operation (eql :areas)))
156   (validate-allocation-area-inclusion-cache)
157   (do-rows (y)
158     (do-pixels-in-row (x)
159       (when (point-in-any-allocation-area-p (tile-absolute-x tile x)
160                                             (tile-absolute-y tile y))
161         (setf (raw-pixel) (apply #'colorize-pixel (raw-pixel) '(220 220 220)))))))
162
163 (defmethod image-tile-process ((tile image-tile) (operation (eql :contracts)))
164   (do-rows (y)
165     (do-pixels-in-row (x)
166       (let* ((m2 (object-at tile (tile-absolute-x tile x) (tile-absolute-y tile y)))
167              (contract (and m2 (m2-contract m2))))
168         (when (and contract (contract-paidp contract))
169           (setf (raw-pixel) (apply #'colorize-pixel (raw-pixel) (contract-color contract))))))))
170
171 (defvar *tile-proc-statistics* (make-statistics-table))
172
173 (defmethod image-tile-process ((tile image-tile) (operation (eql :palette)))
174   (true-color-to-palette))
175
176 (defmethod image-tile-image ((tile image-tile) &optional imageproc-statements)
177   (unless imageproc-statements
178     (setq imageproc-statements '((:background) (:areas) (:contracts) (:palette))))
179   (let ((image (create-image (tile-width tile) (tile-height tile) t)))
180     (with-default-image (image)
181       (fill-image 0 0 :color (find-color 255 255 255))
182       (dolist (statement imageproc-statements)
183         (with-statistics-log (*tile-proc-statistics* (car statement))
184           (apply #'image-tile-process tile statement))))
185     image))
186
187 (defgeneric generate-current-image (tile)
188   (:documentation "Generate the transient image"))
189
190 (defmethod image-tile-changed ((image-tile image-tile) &rest args)
191   (declare (ignore args))
192   (setf (slot-value image-tile 'changed-time) (get-universal-time)))
193
194 (defun image-from-tiles (image-pathname tiles &key if-exists)
195   "Draw an image consisting of the given tiles on a new canvas which encloses all the tiles"
196   (multiple-value-bind
197         (left top width height)
198       (compute-bounding-box (mapcan #'(lambda (tile) (list (cons (tile-nw-x tile) (tile-nw-y tile))
199                                                            (cons (+ +m2tile-width+ (tile-nw-x tile)) (+ +m2tile-width+ (tile-nw-y tile)))))
200                                     tiles))
201     (let ((right (+ left width))
202           (bottom (+ top height)))
203       (with-image (resulting-image width height t)
204         (loop with tile-top = (* +m2tile-width+ (floor top +m2tile-width+))
205            for y from tile-top upto bottom by +m2tile-width+
206            do (loop with tile-left = (* +m2tile-width+ (floor left +m2tile-width+))
207                  for x from tile-left upto right by +m2tile-width+
208                  for tile = (get-map-tile x y)
209                  do (if tile
210                         (progn
211                           (copy-image (image-tile-image tile)
212                                       resulting-image
213                                       0 0
214                                       (- x tile-left) (- y tile-top)
215                                       +m2tile-width+ +m2tile-width+))
216                         (warn "tile at ~D/~D not found?" x y))))
217         (write-image-to-file image-pathname :image resulting-image :if-exists if-exists)
218         t))))
219
220 ;;; Aufteilen der Karte in Kacheln:
221
222                                         ; (split-map +overview-tile-width+ "/home/bknr/tiles-2700/" "/home/bknr/tiles-90/")
223
224 ;;; Laden den Kacheln als BLOBs in die Datenbank:
225
226                                         ; (import-tiles "/data/overview/")
227
228 (defun namstring (x)
229   ;; XXX geht nicht, liefert sogar mit :FOR-INPUT NIL staendig NIL zurueck.
230   ;; (ext:unix-namestring x :for-input nil)
231   (namestring x))
232
233 (defun import-tiles (directory)
234   (bknr.datastore::without-sync ()
235     (dolist (px (directory directory :all nil))
236       (print px)
237       (let ((x (parse-integer (car (last (pathname-directory px)))))
238             (i 0))
239         (dolist (image-pathname (directory px :all nil))
240           (handler-case
241               (let* ((y (parse-integer (pathname-name image-pathname)))
242                      (tile (ensure-original-map-tile x y)))
243                 (when (zerop (mod i 100))
244                   (princ #\.))
245                 (incf i)
246                 (force-output)
247                 (when (original-map-tile-image tile)
248                   (delete-object (original-map-tile-image tile)))
249                 (change-slot-values tile 'image
250                                     (import-image image-pathname :name (format nil "tile-~D-~D" x y))))
251             (error (e)
252               (warn "failed to import ~A: ~A" image-pathname e))))))))
253
254 (defun split-map (width input-directory output-directory)
255   (assert (zerop (mod 2700 width)))
256   (dotimes (y 4)
257     (dotimes (x 4)
258       (print (cons x y))
259       (force-output)
260       (split-image
261        output-directory
262        (merge-pathnames (format nil "sl_utm50s_~2,'0D.png" (+ (* y 4) x 1))
263                         input-directory)
264        width width
265        :offset-x (* x 2700)
266        :offset-y (* y 2700)))))
267
268 (defun split-image (directory full-image-pathname tile-width tile-height
269                     &key (offset-x 0) (offset-y 0) (zoom 1))
270   (cl-gd:with-image-from-file (full full-image-pathname)
271     (cl-gd:with-image (part (* tile-width zoom) (* tile-height zoom) t)
272       (dotimes (i (truncate (cl-gd:image-width full) tile-width))
273         (princ i)
274         (dotimes (j (truncate (cl-gd:image-height full) tile-height))
275           (when (zerop (mod j 100))
276             (princ #\.))
277           (force-output)
278           (let* ((x (* i tile-width))
279                  (y (* j tile-height))
280                  (map-x (+ (* i tile-width) offset-x))
281                  (map-y (+ (* j tile-height) offset-y))
282                  (out (merge-pathnames (make-pathname :directory (list :relative (write-to-string map-x))
283                                                       :name (write-to-string map-y)
284                                                       :type "png")
285                                        directory)))
286             (apply #'cl-gd:copy-image
287                    full part x y 0 0 tile-width tile-height
288                    (if (eql zoom 1)
289                        nil
290                        (list :resize t
291                              :dest-width (* tile-width zoom)
292                              :dest-height (* tile-height zoom))))
293             (ensure-directories-exist out)
294             (cl-gd:write-image-to-file out :type :png :image part)))))))
Note: See TracBrowser for help on using the browser.