| 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-object '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 | (defun point-in-any-allocation-area-p (x-coord y-coord) |
|---|
| 83 | (find-if #'(lambda (allocation-area) |
|---|
| 84 | ;; first check whether point is in bounding box, then do full polygon check |
|---|
| 85 | (and (point-in-polygon-p x-coord y-coord (allocation-area-bounding-box allocation-area)) |
|---|
| 86 | (point-in-polygon-p x-coord y-coord (allocation-area-vertices allocation-area)))) |
|---|
| 87 | (store-objects-with-class 'allocation-area))) |
|---|
| 88 | |
|---|
| 89 | (defclass image-tile (tile) |
|---|
| 90 | ((original-image :documentation "Original satellite image" |
|---|
| 91 | :initform nil) |
|---|
| 92 | (changed-time :initarg :changed-time |
|---|
| 93 | :accessor image-tile-changed-time |
|---|
| 94 | :documentation "Timestamp of last change in contracts pointing to this tile") |
|---|
| 95 | (layers :initarg :layers :reader image-tile-layers)) |
|---|
| 96 | (:default-initargs :type :png :changed-time (get-universal-time) :layers '(background areas contracts palette))) |
|---|
| 97 | |
|---|
| 98 | (defmethod initialize-instance :after ((tile image-tile) &key nw-x nw-y width &allow-other-keys) |
|---|
| 99 | (register-rect-subscriber *rect-publisher* tile |
|---|
| 100 | (list nw-x nw-y width width) #'image-tile-changed)) |
|---|
| 101 | |
|---|
| 102 | (defmethod image-tile-original-image ((tile image-tile)) |
|---|
| 103 | (with-slots (original-image nw-x nw-y) tile |
|---|
| 104 | (unless original-image |
|---|
| 105 | (setf original-image (get-original-image-at nw-x nw-y))) |
|---|
| 106 | original-image)) |
|---|
| 107 | |
|---|
| 108 | (defmethod image-tile-process ((tile image-tile) (operation (eql :background))) |
|---|
| 109 | (when (image-tile-original-image tile) |
|---|
| 110 | (with-store-image (original-image (image-tile-original-image tile)) |
|---|
| 111 | (copy-image original-image *default-image* 0 0 0 0 (image-width) (image-height))))) |
|---|
| 112 | |
|---|
| 113 | (defmethod image-tile-process ((tile image-tile) (operation (eql :areas))) |
|---|
| 114 | (do-rows (y) |
|---|
| 115 | (do-pixels-in-row (x) |
|---|
| 116 | (when (point-in-any-allocation-area-p (tile-absolute-x tile x) |
|---|
| 117 | (tile-absolute-y tile y)) |
|---|
| 118 | (setf (raw-pixel) (apply #'colorize-pixel (raw-pixel) '(220 220 220))))))) |
|---|
| 119 | |
|---|
| 120 | (defmethod image-tile-process ((tile image-tile) (operation (eql :contracts))) |
|---|
| 121 | (do-rows (y) |
|---|
| 122 | (do-pixels-in-row (x) |
|---|
| 123 | (let* ((m2 (object-at tile (tile-absolute-x tile x) (tile-absolute-y tile y))) |
|---|
| 124 | (contract (and m2 (m2-contract m2)))) |
|---|
| 125 | (when (and contract (contract-paidp contract)) |
|---|
| 126 | (setf (raw-pixel) (apply #'colorize-pixel (raw-pixel) (contract-color contract)))))))) |
|---|
| 127 | |
|---|
| 128 | (defvar *tile-proc-statistics* (make-statistics-table)) |
|---|
| 129 | |
|---|
| 130 | (defmethod image-tile-process ((tile image-tile) (operation (eql :palette))) |
|---|
| 131 | (true-color-to-palette)) |
|---|
| 132 | |
|---|
| 133 | (defmethod image-tile-image ((tile image-tile) &optional imageproc-statements) |
|---|
| 134 | (unless imageproc-statements |
|---|
| 135 | (setq imageproc-statements '((:background) (:areas) (:contracts) (:palette)))) |
|---|
| 136 | (let ((image (create-image (tile-width tile) (tile-height tile) t))) |
|---|
| 137 | (with-default-image (image) |
|---|
| 138 | (fill-image 0 0 :color (find-color 255 255 255)) |
|---|
| 139 | (dolist (statement imageproc-statements) |
|---|
| 140 | (with-statistics-log (*tile-proc-statistics* (car statement)) |
|---|
| 141 | (apply #'image-tile-process tile statement)))) |
|---|
| 142 | image)) |
|---|
| 143 | |
|---|
| 144 | (defgeneric generate-current-image (tile) |
|---|
| 145 | (:documentation "Generate the transient image")) |
|---|
| 146 | |
|---|
| 147 | (defmethod image-tile-changed ((image-tile image-tile) contract &optional (time (get-universal-time))) |
|---|
| 148 | (declare (ignore contract)) |
|---|
| 149 | (setf (slot-value image-tile 'changed-time) time)) |
|---|
| 150 | |
|---|
| 151 | (defun image-from-tiles (image-pathname tiles &key if-exists) |
|---|
| 152 | "Draw an image consisting of the given tiles on a new canvas which encloses all the tiles" |
|---|
| 153 | (multiple-value-bind |
|---|
| 154 | (left top width height) |
|---|
| 155 | (compute-bounding-box (mapcan #'(lambda (tile) (list (cons (tile-nw-x tile) (tile-nw-y tile)) |
|---|
| 156 | (cons (+ +m2tile-width+ (tile-nw-x tile)) (+ +m2tile-width+ (tile-nw-y tile))))) |
|---|
| 157 | tiles)) |
|---|
| 158 | (let ((right (+ left width)) |
|---|
| 159 | (bottom (+ top height))) |
|---|
| 160 | (with-image (resulting-image width height t) |
|---|
| 161 | (loop with tile-top = (* +m2tile-width+ (floor top +m2tile-width+)) |
|---|
| 162 | for y from tile-top upto bottom by +m2tile-width+ |
|---|
| 163 | do (loop with tile-left = (* +m2tile-width+ (floor left +m2tile-width+)) |
|---|
| 164 | for x from tile-left upto right by +m2tile-width+ |
|---|
| 165 | for tile = (get-map-tile x y) |
|---|
| 166 | do (if tile |
|---|
| 167 | (progn |
|---|
| 168 | (copy-image (image-tile-image tile) |
|---|
| 169 | resulting-image |
|---|
| 170 | 0 0 |
|---|
| 171 | (- x tile-left) (- y tile-top) |
|---|
| 172 | +m2tile-width+ +m2tile-width+)) |
|---|
| 173 | (warn "tile at ~D/~D not found?" x y)))) |
|---|
| 174 | (write-image-to-file image-pathname :image resulting-image :if-exists if-exists) |
|---|
| 175 | t)))) |
|---|
| 176 | |
|---|
| 177 | ;;; Aufteilen der Karte in Kacheln: |
|---|
| 178 | |
|---|
| 179 | ; (split-map +overview-tile-width+ "/home/bknr/tiles-2700/" "/home/bknr/tiles-90/") |
|---|
| 180 | |
|---|
| 181 | ;;; Laden den Kacheln als BLOBs in die Datenbank: |
|---|
| 182 | |
|---|
| 183 | ; (import-tiles "/data/overview/") |
|---|
| 184 | |
|---|
| 185 | (defun namstring (x) |
|---|
| 186 | ;; XXX geht nicht, liefert sogar mit :FOR-INPUT NIL staendig NIL zurueck. |
|---|
| 187 | ;; (ext:unix-namestring x :for-input nil) |
|---|
| 188 | (namestring x)) |
|---|
| 189 | |
|---|
| 190 | (defun import-tiles (directory) |
|---|
| 191 | (bknr.datastore::without-sync () |
|---|
| 192 | (dolist (px (directory directory :all nil)) |
|---|
| 193 | (print px) |
|---|
| 194 | (let ((x (parse-integer (car (last (pathname-directory px))))) |
|---|
| 195 | (i 0)) |
|---|
| 196 | (dolist (image-pathname (directory px :all nil)) |
|---|
| 197 | (handler-case |
|---|
| 198 | (let* ((y (parse-integer (pathname-name image-pathname))) |
|---|
| 199 | (tile (ensure-original-map-tile x y))) |
|---|
| 200 | (when (zerop (mod i 100)) |
|---|
| 201 | (princ #\.)) |
|---|
| 202 | (incf i) |
|---|
| 203 | (force-output) |
|---|
| 204 | (when (original-map-tile-image tile) |
|---|
| 205 | (delete-object (original-map-tile-image tile))) |
|---|
| 206 | (change-slot-values tile 'image |
|---|
| 207 | (import-image image-pathname :name (format nil "tile-~D-~D" x y)))) |
|---|
| 208 | (error (e) |
|---|
| 209 | (warn "failed to import ~A: ~A" image-pathname e)))))))) |
|---|
| 210 | |
|---|
| 211 | (defun split-map (width input-directory output-directory) |
|---|
| 212 | (assert (zerop (mod 2700 width))) |
|---|
| 213 | (dotimes (y 4) |
|---|
| 214 | (dotimes (x 4) |
|---|
| 215 | (print (cons x y)) |
|---|
| 216 | (force-output) |
|---|
| 217 | (split-image |
|---|
| 218 | output-directory |
|---|
| 219 | (merge-pathnames (format nil "sl_utm50s_~2,'0D.png" (+ (* y 4) x 1)) |
|---|
| 220 | input-directory) |
|---|
| 221 | width width |
|---|
| 222 | :offset-x (* x 2700) |
|---|
| 223 | :offset-y (* y 2700))))) |
|---|
| 224 | |
|---|
| 225 | (defun split-image (directory full-image-pathname tile-width tile-height |
|---|
| 226 | &key (offset-x 0) (offset-y 0) (zoom 1)) |
|---|
| 227 | (cl-gd:with-image-from-file (full full-image-pathname) |
|---|
| 228 | (cl-gd:with-image (part (* tile-width zoom) (* tile-height zoom) t) |
|---|
| 229 | (dotimes (i (truncate (cl-gd:image-width full) tile-width)) |
|---|
| 230 | (princ i) |
|---|
| 231 | (dotimes (j (truncate (cl-gd:image-height full) tile-height)) |
|---|
| 232 | (when (zerop (mod j 100)) |
|---|
| 233 | (princ #\.)) |
|---|
| 234 | (force-output) |
|---|
| 235 | (let* ((x (* i tile-width)) |
|---|
| 236 | (y (* j tile-height)) |
|---|
| 237 | (map-x (+ (* i tile-width) offset-x)) |
|---|
| 238 | (map-y (+ (* j tile-height) offset-y)) |
|---|
| 239 | (out (merge-pathnames (make-pathname :directory (list :relative (write-to-string map-x)) |
|---|
| 240 | :name (write-to-string map-y) |
|---|
| 241 | :type "png") |
|---|
| 242 | directory))) |
|---|
| 243 | (apply #'cl-gd:copy-image |
|---|
| 244 | full part x y 0 0 tile-width tile-height |
|---|
| 245 | (if (eql zoom 1) |
|---|
| 246 | nil |
|---|
| 247 | (list :resize t |
|---|
| 248 | :dest-width (* tile-width zoom) |
|---|
| 249 | :dest-height (* tile-height zoom)))) |
|---|
| 250 | (ensure-directories-exist out) |
|---|
| 251 | (cl-gd:write-image-to-file out :type :png :image part))))))) |
|---|