| 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))))))) |
|---|