| 1 | (in-package :bos.m2) |
|---|
| 2 | |
|---|
| 3 | ;;;; M2-STORE |
|---|
| 4 | |
|---|
| 5 | (defvar *m2-store* nil) |
|---|
| 6 | |
|---|
| 7 | (defclass m2-store (mp-store) |
|---|
| 8 | ((tile-index :reader m2-store-tile-index))) |
|---|
| 9 | |
|---|
| 10 | (defmethod initialize-instance :before ((store m2-store) &key &allow-other-keys) |
|---|
| 11 | (when *m2-store* |
|---|
| 12 | (warn "reinitializing m2-store object")) |
|---|
| 13 | (setq *m2-store* store) |
|---|
| 14 | (setf (slot-value store 'tile-index) |
|---|
| 15 | (indexed-class-index-named (find-class 'm2) 'm2-index))) |
|---|
| 16 | |
|---|
| 17 | (defvar *store-transient-init-functions* nil) |
|---|
| 18 | |
|---|
| 19 | (defun register-store-transient-init-function (function-name) |
|---|
| 20 | (check-type function-name symbol) |
|---|
| 21 | (pushnew function-name *store-transient-init-functions*)) |
|---|
| 22 | |
|---|
| 23 | (defmethod bknr.datastore::restore-store :after ((store m2-store) &key until) |
|---|
| 24 | (declare (ignore store until)) |
|---|
| 25 | (dolist (function-name *store-transient-init-functions*) |
|---|
| 26 | (funcall function-name))) |
|---|
| 27 | |
|---|
| 28 | (defun get-map-tile (x y) |
|---|
| 29 | (get-tile (m2-store-tile-index *m2-store*) x y)) |
|---|
| 30 | |
|---|
| 31 | (defun ensure-map-tile (x y) |
|---|
| 32 | (ensure-tile (m2-store-tile-index *m2-store*) x y)) |
|---|
| 33 | |
|---|
| 34 | ;;;; M2 |
|---|
| 35 | |
|---|
| 36 | ;;; Exportierte Funktionen: |
|---|
| 37 | ;;; |
|---|
| 38 | ;;; M2-CONTRACT (m2) => contract or NIL |
|---|
| 39 | ;;; M2-NUM (m2) => integer |
|---|
| 40 | ;;; M2-PRINTABLE (m2) => string |
|---|
| 41 | ;;; M2-X (m2) => integer |
|---|
| 42 | ;;; M2-Y (m2) => integer |
|---|
| 43 | ;;; M2-UTM-X (m2) => double-float |
|---|
| 44 | ;;; M2-UTM-y (m2) => double-float |
|---|
| 45 | ;;; |
|---|
| 46 | ;;; GET-M2 (x y) => m2 or NIL |
|---|
| 47 | ;;; ENSURE-M2 (x y) => m2 |
|---|
| 48 | ;;; GET-M2-WITH-NUM (sqm-num) => m2 or nil |
|---|
| 49 | ;;; ENSURE-M2-WITH-NUM (sqm-num) => m2 |
|---|
| 50 | |
|---|
| 51 | (define-persistent-class m2 () |
|---|
| 52 | ((x :read) |
|---|
| 53 | (y :read) |
|---|
| 54 | (contract :update :relaxed-object-reference t) |
|---|
| 55 | (my-slot :read)) |
|---|
| 56 | (:default-initargs :contract nil) |
|---|
| 57 | (:class-indices (m2-index :index-type tiled-index |
|---|
| 58 | :slots (x y) |
|---|
| 59 | :index-reader m2-at |
|---|
| 60 | :index-initargs (:width +width+ |
|---|
| 61 | :height +width+ |
|---|
| 62 | :tile-size +m2tile-width+ |
|---|
| 63 | :tile-class 'image-tile)))) |
|---|
| 64 | |
|---|
| 65 | (defmethod print-object ((object m2) stream) |
|---|
| 66 | (print-unreadable-object (object stream :type t :identity nil) |
|---|
| 67 | (format stream "at (~D,~D), ~A" |
|---|
| 68 | (m2-x object) |
|---|
| 69 | (m2-y object) |
|---|
| 70 | (if (m2-contract object) "sold" "free")))) |
|---|
| 71 | |
|---|
| 72 | (defun get-m2 (&rest coords) |
|---|
| 73 | (m2-at coords)) |
|---|
| 74 | |
|---|
| 75 | (defun ensure-m2 (&rest coords) |
|---|
| 76 | (or (m2-at coords) |
|---|
| 77 | (destructuring-bind (x y) coords |
|---|
| 78 | (make-instance 'm2 :x x :y y)))) |
|---|
| 79 | |
|---|
| 80 | (defmethod get-m2-with-num ((num integer)) |
|---|
| 81 | (multiple-value-bind (y x) (truncate num +width+) |
|---|
| 82 | (get-m2 x y))) |
|---|
| 83 | |
|---|
| 84 | (defmethod get-m2-with-num ((num string)) |
|---|
| 85 | (get-m2-with-num (parse-integer num :radix 36))) |
|---|
| 86 | |
|---|
| 87 | (defmethod ensure-m2-with-num ((num integer)) |
|---|
| 88 | (multiple-value-bind (y x) (truncate num +width+) |
|---|
| 89 | (ensure-m2 x y))) |
|---|
| 90 | |
|---|
| 91 | (defmethod ensure-m2-with-num ((num string)) |
|---|
| 92 | (ensure-m2-with-num (parse-integer num :radix 36))) |
|---|
| 93 | |
|---|
| 94 | (defun m2-num (m2) |
|---|
| 95 | "Fortlaufende Quadratmeternummer in row-major-order." |
|---|
| 96 | (+ (* (m2-y m2) +width+) (m2-x m2))) |
|---|
| 97 | |
|---|
| 98 | (defun m2-num-string (m2) |
|---|
| 99 | "Quadratmeternummer im druckbaren Format (Radix 36, 6 Zeichen lang)" |
|---|
| 100 | (format nil "~36,6,'0R" (m2-num m2))) |
|---|
| 101 | |
|---|
| 102 | ;; UTM laeuft von links nach rechts und von UNTEN NACH OBEN. |
|---|
| 103 | (defun m2-utm-x (m2) (+ +nw-utm-x+ (m2-x m2))) |
|---|
| 104 | (defun m2-utm-y (m2) (- +nw-utm-y+ (m2-y m2))) |
|---|
| 105 | (defun m2-utm (m2) (list (m2-utm-x m2) (m2-utm-y m2))) |
|---|
| 106 | |
|---|
| 107 | (defun m2-lon-lat (m2) |
|---|
| 108 | (geo-utm:utm-x-y-to-lon-lat (m2-utm-x m2) (m2-utm-y m2) +utm-zone+ t)) |
|---|
| 109 | |
|---|
| 110 | (defmethod m2-num-to-utm ((num integer)) |
|---|
| 111 | (multiple-value-bind (y x) (truncate num +width+) |
|---|
| 112 | (+ +nw-utm-x+ x) |
|---|
| 113 | (- +nw-utm-y+ y))) |
|---|
| 114 | |
|---|
| 115 | (defmethod m2-num-to-utm ((num string)) |
|---|
| 116 | (m2-num-to-utm (parse-integer num :radix 36))) |
|---|
| 117 | |
|---|
| 118 | (defmethod m2-allocation-area ((m2 m2)) |
|---|
| 119 | (find-if #'(lambda (allocation-area) (point-in-polygon-p (m2-x m2) (m2-y m2) (allocation-area-vertices allocation-area))) |
|---|
| 120 | (class-instances 'allocation-area))) |
|---|
| 121 | |
|---|
| 122 | (defun m2s-polygon (m2s) |
|---|
| 123 | (let* ((m2 (first m2s)) |
|---|
| 124 | (contract (m2-contract m2))) |
|---|
| 125 | (region-to-polygon (list (m2-x m2) (m2-y m2)) |
|---|
| 126 | (lambda (p) |
|---|
| 127 | (let ((m2 (apply #'get-m2 p))) |
|---|
| 128 | (and m2 (eql contract (m2-contract m2)))))))) |
|---|
| 129 | |
|---|
| 130 | (defun m2s-polygon-lon-lat (m2s) |
|---|
| 131 | (let ((polygon (m2s-polygon m2s))) |
|---|
| 132 | (mapcar (lambda (point) |
|---|
| 133 | (destructuring-bind (x y) point |
|---|
| 134 | (geo-utm:utm-x-y-to-lon-lat (+ +nw-utm-x+ x) (- +nw-utm-y+ y) +utm-zone+ t))) |
|---|
| 135 | polygon))) |
|---|
| 136 | |
|---|
| 137 | ;;;; SPONSOR |
|---|
| 138 | |
|---|
| 139 | ;;; Exportierte Funktionen: |
|---|
| 140 | ;;; |
|---|
| 141 | ;;; MAKE-SPONSOR (&rest initargs) => sponsor |
|---|
| 142 | ;;; (Automatisch Zuweisung eines Login-Namens.) |
|---|
| 143 | ;;; |
|---|
| 144 | ;;; SPONSOR-PASSWORD-QUESTION (sponsor) => string |
|---|
| 145 | ;;; SPONSOR-PASSWORD-ANSWER (sponsor) => string |
|---|
| 146 | ;;; SPONSOR-INFO-TEXT (sponsor) => string |
|---|
| 147 | ;;; SPONSOR-COUNTRY (sponsor) => string |
|---|
| 148 | ;;; SPONSOR-LANGUAGE (sponsor) => string (preferred language) |
|---|
| 149 | ;;; SPONSOR-CONTRACTS (sponsor) => list of contract |
|---|
| 150 | ;;; |
|---|
| 151 | ;;; Sowie Funktionen von USER. |
|---|
| 152 | |
|---|
| 153 | (define-persistent-class sponsor (user) |
|---|
| 154 | ((master-code :read :initform nil) |
|---|
| 155 | (info-text :update :initform nil) |
|---|
| 156 | (country :update :initform nil) |
|---|
| 157 | (contracts :update :initform nil) |
|---|
| 158 | (language :update :initform nil)) |
|---|
| 159 | (:default-initargs :full-name nil :email nil)) |
|---|
| 160 | |
|---|
| 161 | (defmethod user-editable-p ((sponsor sponsor)) |
|---|
| 162 | nil) |
|---|
| 163 | |
|---|
| 164 | (defun sponsor-p (object) |
|---|
| 165 | (equal (class-of object) (find-class 'sponsor))) |
|---|
| 166 | |
|---|
| 167 | (deftransaction sponsor-set-info-text (sponsor newval) |
|---|
| 168 | (setf (sponsor-info-text sponsor) newval)) |
|---|
| 169 | |
|---|
| 170 | (deftransaction sponsor-set-country (sponsor newval) |
|---|
| 171 | (setf (sponsor-country sponsor) newval)) |
|---|
| 172 | |
|---|
| 173 | (deftransaction sponsor-set-language (sponsor newval) |
|---|
| 174 | (setf (sponsor-language sponsor) newval)) |
|---|
| 175 | |
|---|
| 176 | (defmethod sponsor-language :around ((sponsor sponsor)) |
|---|
| 177 | (or (call-next-method) |
|---|
| 178 | "en")) |
|---|
| 179 | |
|---|
| 180 | (defvar *sponsor-counter* 0) |
|---|
| 181 | |
|---|
| 182 | (defun make-sponsor (&rest initargs &key login &allow-other-keys) |
|---|
| 183 | (apply #'make-object 'sponsor |
|---|
| 184 | :login (or login (format nil "s-~36R-~36R" (incf *sponsor-counter*) (get-universal-time))) |
|---|
| 185 | :master-code (mod (+ (get-universal-time) (random 1000000)) 1000000) |
|---|
| 186 | initargs)) |
|---|
| 187 | |
|---|
| 188 | (defun sponsor-consistent-p (sponsor) |
|---|
| 189 | (labels ((contract-points-to-sponsor (contract) |
|---|
| 190 | (eq sponsor (contract-sponsor contract)))) |
|---|
| 191 | (let ((consistent t)) |
|---|
| 192 | (unless (every #'contract-points-to-sponsor (sponsor-contracts sponsor)) |
|---|
| 193 | (let ((*print-length* 5)) |
|---|
| 194 | (warn "~s of ~s dont point to it by CONTRACT-SPONSOR~ |
|---|
| 195 | ~%the wrongly pointed to objs with duplicates removed are: ~s" |
|---|
| 196 | (remove-if #'contract-points-to-sponsor (sponsor-contracts sponsor)) |
|---|
| 197 | sponsor |
|---|
| 198 | (remove-duplicates (remove sponsor (mapcar #'contract-sponsor (sponsor-contracts sponsor)))))) |
|---|
| 199 | (setq consistent nil)) |
|---|
| 200 | consistent))) |
|---|
| 201 | |
|---|
| 202 | (defmethod destroy-object :before ((sponsor sponsor)) |
|---|
| 203 | (mapc #'delete-object (sponsor-contracts sponsor))) |
|---|
| 204 | |
|---|
| 205 | (defmethod sponsor-id ((sponsor sponsor)) |
|---|
| 206 | (store-object-id sponsor)) |
|---|
| 207 | |
|---|
| 208 | (define-user-flag :editor) |
|---|
| 209 | |
|---|
| 210 | (defmethod editor-p ((user user)) |
|---|
| 211 | (or (admin-p user) |
|---|
| 212 | (user-has-flag user :editor))) |
|---|
| 213 | |
|---|
| 214 | (defmethod editor-p ((user null)) |
|---|
| 215 | nil) |
|---|
| 216 | |
|---|
| 217 | (defclass editor-only-handler () |
|---|
| 218 | ()) |
|---|
| 219 | |
|---|
| 220 | (defmethod bknr.web:authorized-p ((handler editor-only-handler)) |
|---|
| 221 | (editor-p (bknr.web:bknr-session-user))) |
|---|
| 222 | |
|---|
| 223 | ;;;; CONTRACT |
|---|
| 224 | |
|---|
| 225 | ;;; Exportierte Funktionen: |
|---|
| 226 | ;;; |
|---|
| 227 | ;;; MAKE-CONTRACT (sponsor m2s) => contract |
|---|
| 228 | ;;; |
|---|
| 229 | ;;; GET-CONTRACT (id) => contract |
|---|
| 230 | ;;; |
|---|
| 231 | ;;; CONTRACT-SPONSOR (contract) => sponsor |
|---|
| 232 | ;;; CONTRACT-PAIDP (contract) => boolean |
|---|
| 233 | ;;; CONTRACT-DATE (contract) => Universal-Timestamp |
|---|
| 234 | ;;; CONTRACT-M2S (contract) => list of m2 |
|---|
| 235 | ;;; CONTRACT-BOUNDING-BOX (contract) => (list left top width height) |
|---|
| 236 | ;;; |
|---|
| 237 | ;;; CONTRACT-SET-PAIDP (contract newval) => newval |
|---|
| 238 | |
|---|
| 239 | (defvar *claim-colors* '((0 0 128) |
|---|
| 240 | (0 128 0) |
|---|
| 241 | (0 128 128) |
|---|
| 242 | (128 0 0) |
|---|
| 243 | (128 0 128) |
|---|
| 244 | (128 128 0) |
|---|
| 245 | (0 0 255) |
|---|
| 246 | (0 255 0) |
|---|
| 247 | (0 255 255) |
|---|
| 248 | (255 0 0) |
|---|
| 249 | (255 0 255) |
|---|
| 250 | (255 255 0))) |
|---|
| 251 | |
|---|
| 252 | (define-persistent-class contract () |
|---|
| 253 | ((sponsor :read :relaxed-object-reference t) |
|---|
| 254 | (date :read) |
|---|
| 255 | (paidp :update) |
|---|
| 256 | (m2s :read) |
|---|
| 257 | (color :read) |
|---|
| 258 | (download-only :update) |
|---|
| 259 | (cert-issued :read) |
|---|
| 260 | (worldpay-trans-id :update :initform nil) |
|---|
| 261 | (expires :read :documentation "universal time which specifies the time the contract expires (is deleted) when it has not been paid for" :initform nil) |
|---|
| 262 | (largest-rectangle :update)) |
|---|
| 263 | (:default-initargs |
|---|
| 264 | :m2s nil |
|---|
| 265 | :paidp nil |
|---|
| 266 | :download-only nil |
|---|
| 267 | :color (random-elt *claim-colors*) |
|---|
| 268 | :cert-issued nil |
|---|
| 269 | :expires (+ (get-universal-time) *manual-contract-expiry-time*))) |
|---|
| 270 | |
|---|
| 271 | (defmethod print-object ((object contract) stream) |
|---|
| 272 | (print-unreadable-object (object stream :type t :identity nil) |
|---|
| 273 | (format stream "ID: ~D, ~A" |
|---|
| 274 | (store-object-id object) |
|---|
| 275 | (if (contract-paidp object) "paid" "unpaid")))) |
|---|
| 276 | |
|---|
| 277 | (defun contract-p (object) |
|---|
| 278 | (equal (class-of object) (find-class 'contract))) |
|---|
| 279 | |
|---|
| 280 | (defmethod initialize-persistent-instance :after ((contract contract)) |
|---|
| 281 | (pushnew contract (sponsor-contracts (contract-sponsor contract))) |
|---|
| 282 | (publish-contract-change contract) |
|---|
| 283 | (dolist (m2 (contract-m2s contract)) |
|---|
| 284 | (setf (m2-contract m2) contract)) |
|---|
| 285 | (setf (contract-largest-rectangle contract) |
|---|
| 286 | (contract-compute-largest-rectangle contract))) |
|---|
| 287 | |
|---|
| 288 | (defmethod destroy-object :before ((contract contract)) |
|---|
| 289 | (let ((sponsor (contract-sponsor contract))) |
|---|
| 290 | (when sponsor |
|---|
| 291 | (setf (sponsor-contracts sponsor) (remove contract (sponsor-contracts sponsor))))) |
|---|
| 292 | (publish-contract-change contract) |
|---|
| 293 | (dolist (m2 (contract-m2s contract)) |
|---|
| 294 | (setf (m2-contract m2) nil)) |
|---|
| 295 | (return-contract-m2s (contract-m2s contract))) |
|---|
| 296 | |
|---|
| 297 | (defun get-contract (id) |
|---|
| 298 | (let ((contract (store-object-with-id id))) |
|---|
| 299 | (prog1 |
|---|
| 300 | contract |
|---|
| 301 | (unless (subtypep (type-of contract) 'contract) |
|---|
| 302 | (error "invalid contract id (wrong type) ~A" id))))) |
|---|
| 303 | |
|---|
| 304 | (defun publish-contract-change (contract) |
|---|
| 305 | "Note: This routine is to reinitialize the transient contract tree |
|---|
| 306 | and thus may be called more than once." |
|---|
| 307 | (publish-rect-change *rect-publisher* (contract-bounding-box contract) contract)) |
|---|
| 308 | |
|---|
| 309 | (defmethod contract-is-expired ((contract contract)) |
|---|
| 310 | (and (contract-expires contract) |
|---|
| 311 | (> (get-universal-time) (contract-expires contract)))) |
|---|
| 312 | |
|---|
| 313 | (deftransaction contract-set-paidp (contract newval) |
|---|
| 314 | (setf (contract-paidp contract) newval) |
|---|
| 315 | (publish-contract-change contract) |
|---|
| 316 | (add-contract-to-cache contract) |
|---|
| 317 | (bknr.rss::add-item "news" contract)) |
|---|
| 318 | |
|---|
| 319 | (defmethod contract-price ((contract contract)) |
|---|
| 320 | (* (length (contract-m2s contract)) +price-per-m2+)) |
|---|
| 321 | |
|---|
| 322 | (defmethod contract-download-only-p ((contract contract)) |
|---|
| 323 | (or (contract-download-only contract) |
|---|
| 324 | (< (contract-price contract) *mail-amount*))) |
|---|
| 325 | |
|---|
| 326 | (deftransaction contract-set-download-only-p (contract newval) |
|---|
| 327 | (setf (contract-download-only contract) newval)) |
|---|
| 328 | |
|---|
| 329 | (defmethod contract-fdf-pathname ((contract contract) &key language print) |
|---|
| 330 | (when (and print |
|---|
| 331 | (contract-download-only-p contract)) |
|---|
| 332 | (error "no print fdf for download-only contract ~A" contract)) |
|---|
| 333 | (merge-pathnames (make-pathname :name (format nil "~D-~(~A~)" |
|---|
| 334 | (store-object-id contract) |
|---|
| 335 | language) |
|---|
| 336 | :type "fdf") |
|---|
| 337 | (if print *cert-mail-directory* *cert-download-directory*))) |
|---|
| 338 | |
|---|
| 339 | (defmethod contract-pdf-pathname ((contract contract) &key print) |
|---|
| 340 | (merge-pathnames (make-pathname :name (format nil "~D" (store-object-id contract)) |
|---|
| 341 | :type "pdf") |
|---|
| 342 | (if print bos.m2::*cert-mail-directory* bos.m2::*cert-download-directory*))) |
|---|
| 343 | |
|---|
| 344 | (defmethod contract-pdf-url ((contract contract)) |
|---|
| 345 | (format nil "/certificate/~A" (store-object-id contract))) |
|---|
| 346 | |
|---|
| 347 | (defmethod contract-certificates-generated-p (contract) |
|---|
| 348 | (and (probe-file (contract-pdf-pathname contract)) |
|---|
| 349 | (or (contract-download-only-p contract) |
|---|
| 350 | (probe-file (contract-pdf-pathname contract :print t))))) |
|---|
| 351 | |
|---|
| 352 | (defmethod contract-delete-certificate-files (contract) |
|---|
| 353 | (ignore-errors |
|---|
| 354 | (delete-file (contract-pdf-pathname contract)) |
|---|
| 355 | (delete-file (contract-pdf-pathname contract :print t)))) |
|---|
| 356 | |
|---|
| 357 | (defun wait-for-certificates (contract) |
|---|
| 358 | "Wait until the PDF generating process has generated the certificates" |
|---|
| 359 | (dotimes (i 10) |
|---|
| 360 | (when (contract-certificates-generated-p contract) |
|---|
| 361 | (return)) |
|---|
| 362 | (sleep 1)) |
|---|
| 363 | (unless (contract-certificates-generated-p contract) |
|---|
| 364 | (error "Cannot generate certificate"))) |
|---|
| 365 | |
|---|
| 366 | (defmethod contract-issue-cert ((contract contract) name &key address language) |
|---|
| 367 | (when (contract-cert-issued contract) |
|---|
| 368 | (warn "re-issuing cert for ~A" contract)) |
|---|
| 369 | (contract-delete-certificate-files contract) |
|---|
| 370 | (make-certificate contract name :address address :language language) |
|---|
| 371 | (unless (contract-download-only-p contract) |
|---|
| 372 | (make-certificate contract name :address address :language language :print t)) |
|---|
| 373 | (wait-for-certificates contract) |
|---|
| 374 | (change-slot-values contract 'cert-issued t)) |
|---|
| 375 | |
|---|
| 376 | (defmethod contract-image-tiles ((contract contract)) |
|---|
| 377 | (let (image-tiles) |
|---|
| 378 | (dolist (m2 (contract-m2s contract)) |
|---|
| 379 | (pushnew (get-map-tile (m2-x m2) (m2-y m2)) |
|---|
| 380 | image-tiles)) |
|---|
| 381 | image-tiles)) |
|---|
| 382 | |
|---|
| 383 | (defmethod contract-bounding-box ((contract contract)) |
|---|
| 384 | (geometry:with-bounding-box-collect (collect) |
|---|
| 385 | (dolist (m2 (contract-m2s contract)) |
|---|
| 386 | (collect (list (m2-x m2) (m2-y m2)))))) |
|---|
| 387 | |
|---|
| 388 | (defun contracts-bounding-box (&optional (contracts (all-contracts))) |
|---|
| 389 | (geometry:with-bounding-box-collect (collect) |
|---|
| 390 | (dolist (contract contracts) |
|---|
| 391 | (dolist (m2 (contract-m2s contract)) |
|---|
| 392 | (collect (list (m2-x m2) (m2-y m2))))))) |
|---|
| 393 | |
|---|
| 394 | (defun contract-area (contract) |
|---|
| 395 | (length (contract-m2s contract))) |
|---|
| 396 | |
|---|
| 397 | (defun contract-polygon (contract) |
|---|
| 398 | (m2s-polygon (contract-m2s contract))) |
|---|
| 399 | |
|---|
| 400 | (defun contract-compute-largest-rectangle (contract) |
|---|
| 401 | (macrolet ((when-scaling-needed (arg &body body) |
|---|
| 402 | `(if (= scaler 1) |
|---|
| 403 | ,arg |
|---|
| 404 | (progn ,@body)))) |
|---|
| 405 | (let* ((m2s (contract-m2s contract)) |
|---|
| 406 | (area (length m2s)) |
|---|
| 407 | (scaler (ceiling area 1000.0)) |
|---|
| 408 | (bounding-box (contract-bounding-box contract))) |
|---|
| 409 | (geometry:with-rectangle bounding-box |
|---|
| 410 | (declare (ignore width height)) |
|---|
| 411 | (labels ( ;; to-orig |
|---|
| 412 | (distance-to-orig (d) |
|---|
| 413 | (when-scaling-needed d |
|---|
| 414 | (round (* d scaler)))) |
|---|
| 415 | (x-coordinate-to-orig (x) |
|---|
| 416 | (when-scaling-needed x |
|---|
| 417 | (+ left (round (* (- x left) scaler))))) |
|---|
| 418 | (y-coordinate-to-orig (y) |
|---|
| 419 | (when-scaling-needed y |
|---|
| 420 | (+ top (round (* (- y top) scaler))))) |
|---|
| 421 | (rectangle-to-orig (r) |
|---|
| 422 | (when-scaling-needed r |
|---|
| 423 | (geometry:with-rectangle r |
|---|
| 424 | (list (x-coordinate-to-orig left) |
|---|
| 425 | (y-coordinate-to-orig top) |
|---|
| 426 | (distance-to-orig width) |
|---|
| 427 | (distance-to-orig height))))) |
|---|
| 428 | ;; from-orig |
|---|
| 429 | (distance-from-orig (d) |
|---|
| 430 | (when-scaling-needed d |
|---|
| 431 | (floor d scaler))) |
|---|
| 432 | (x-coordinate-from-orig (x) |
|---|
| 433 | (when-scaling-needed x |
|---|
| 434 | (+ left (floor (- x left) scaler)))) |
|---|
| 435 | (y-coordinate-from-orig (y) |
|---|
| 436 | (when-scaling-needed y |
|---|
| 437 | (+ top (floor (- y top) scaler)))) |
|---|
| 438 | (rectangle-from-orig (r) |
|---|
| 439 | (when-scaling-needed r |
|---|
| 440 | (geometry:with-rectangle r |
|---|
| 441 | (list (x-coordinate-from-orig left) |
|---|
| 442 | (y-coordinate-from-orig top) |
|---|
| 443 | (distance-from-orig width) |
|---|
| 444 | (distance-from-orig height)))))) |
|---|
| 445 | (rectangle-to-orig |
|---|
| 446 | (screamer-user:largest-rectangle |
|---|
| 447 | (rectangle-from-orig bounding-box) |
|---|
| 448 | (lambda (x y) |
|---|
| 449 | (let ((m2 (get-m2 (x-coordinate-to-orig x) (y-coordinate-to-orig y)))) |
|---|
| 450 | (and m2 (eql contract (m2-contract m2)))))))))))) |
|---|
| 451 | |
|---|
| 452 | (defun contract-neighbours (contract) |
|---|
| 453 | "Return all contracts that have an adjacent m2 to one of CONTRACT's |
|---|
| 454 | m2s." |
|---|
| 455 | (let (contracts) |
|---|
| 456 | (flet ((push-neighbour (x y) |
|---|
| 457 | (let ((m2 (get-m2 x y))) |
|---|
| 458 | (when (and m2 |
|---|
| 459 | (m2-contract m2) |
|---|
| 460 | (not (eq (m2-contract m2) contract)) |
|---|
| 461 | (pushnew (m2-contract m2) contracts)))))) |
|---|
| 462 | (dolist (m2 (contract-m2s contract) contracts) |
|---|
| 463 | (let ((x (m2-x m2)) |
|---|
| 464 | (y (m2-y m2))) |
|---|
| 465 | (push-neighbour (1- x) (1- y)) |
|---|
| 466 | (push-neighbour (1- x) (1+ y)) |
|---|
| 467 | (push-neighbour (1+ x) (1- y)) |
|---|
| 468 | (push-neighbour (1+ x) (1+ y))))))) |
|---|
| 469 | |
|---|
| 470 | (defun contract-center (contract) |
|---|
| 471 | (destructuring-bind (left top width height) |
|---|
| 472 | (contract-largest-rectangle contract) |
|---|
| 473 | (rectangle-center (list left top width height) :roundp nil))) |
|---|
| 474 | |
|---|
| 475 | (defun contract-center-lon-lat (contract) |
|---|
| 476 | (error "this function is deprecated") |
|---|
| 477 | (let ((center (contract-center contract))) |
|---|
| 478 | (with-points (center) |
|---|
| 479 | (geo-utm:utm-x-y-to-lon-lat (+ +nw-utm-x+ center-x) (- +nw-utm-y+ center-y) +utm-zone+ t)))) |
|---|
| 480 | |
|---|
| 481 | (defun tx-make-contract (sponsor m2-count &key date paidp expires) |
|---|
| 482 | (warn "Old tx-make-contract transaction used, contract dates may be wrong") |
|---|
| 483 | (tx-do-make-contract sponsor m2-count :date date :paidp paidp :expires expires)) |
|---|
| 484 | |
|---|
| 485 | (deftransaction do-make-contract (sponsor m2-count &key date paidp expires download-only) |
|---|
| 486 | (let ((m2s (allocate-m2s-for-sale m2-count))) |
|---|
| 487 | (if m2s |
|---|
| 488 | (let ((contract (make-object 'contract |
|---|
| 489 | :sponsor sponsor |
|---|
| 490 | :date date |
|---|
| 491 | :m2s m2s |
|---|
| 492 | :expires expires |
|---|
| 493 | :download-only download-only))) |
|---|
| 494 | (when paidp |
|---|
| 495 | (contract-set-paidp contract paidp)) |
|---|
| 496 | contract) |
|---|
| 497 | (warn "can't create contract, ~A square meters for ~A could not be allocated" m2-count sponsor)))) |
|---|
| 498 | |
|---|
| 499 | (define-condition allocation-areas-exhausted (simple-error) |
|---|
| 500 | ((numsqm :initarg :numsqm :reader numsqm)) |
|---|
| 501 | (:report (lambda (condition stream) |
|---|
| 502 | (format stream "Could not satisfy your request for ~A sqms, please contact the BOS office" |
|---|
| 503 | (numsqm condition))))) |
|---|
| 504 | |
|---|
| 505 | (defun make-contract (sponsor m2-count |
|---|
| 506 | &key (date (get-universal-time)) |
|---|
| 507 | paidp |
|---|
| 508 | (expires (+ (get-universal-time) *manual-contract-expiry-time*)) |
|---|
| 509 | download-only) |
|---|
| 510 | (unless (and (integerp m2-count) |
|---|
| 511 | (plusp m2-count)) |
|---|
| 512 | (error "number of square meters must be a positive integer")) |
|---|
| 513 | (let ((contract (do-make-contract sponsor m2-count |
|---|
| 514 | :date date |
|---|
| 515 | :paidp paidp |
|---|
| 516 | :expires expires |
|---|
| 517 | :download-only download-only))) |
|---|
| 518 | (unless contract |
|---|
| 519 | (send-system-mail :subject "Contact creation failed - Allocation areas exhaused" |
|---|
| 520 | :text (format nil "A contract for ~A square meters could not be created, presumably because no |
|---|
| 521 | suitable allocation area was found. Please check the free allocation |
|---|
| 522 | areas and add more space. |
|---|
| 523 | |
|---|
| 524 | Sponsor-ID: ~A |
|---|
| 525 | " |
|---|
| 526 | m2-count (store-object-id sponsor))) |
|---|
| 527 | (error 'allocation-areas-exhausted :numsqm m2-count)) |
|---|
| 528 | contract)) |
|---|
| 529 | |
|---|
| 530 | (deftransaction recolorize-contracts (&optional colors) |
|---|
| 531 | "Assigns a new color to each contract choosing from COLORS, so |
|---|
| 532 | that CONTRACTS-WELL-COLORED-P holds." |
|---|
| 533 | (let ((contracts (class-instances 'contract))) |
|---|
| 534 | (loop for contract in contracts |
|---|
| 535 | for color in (screamer-user:colorize colors contracts #'contract-neighbours) |
|---|
| 536 | do (setf (slot-value contract 'color) color) |
|---|
| 537 | do (publish-contract-change contract)))) |
|---|
| 538 | |
|---|
| 539 | (defun contracts-well-colored-p () |
|---|
| 540 | "Checks if all contracts have a different color than all their |
|---|
| 541 | neighbours." |
|---|
| 542 | (loop for contract in (class-instances 'contract) |
|---|
| 543 | do (when (member (contract-color contract) (contract-neighbours contract) |
|---|
| 544 | :key #'contract-color :test #'equal) |
|---|
| 545 | (return nil)) |
|---|
| 546 | finally (return t))) |
|---|
| 547 | |
|---|
| 548 | (defun contract-consistent-p (contract) |
|---|
| 549 | (labels ((m2-points-to-contract (m2) |
|---|
| 550 | (eq contract (m2-contract m2)))) |
|---|
| 551 | (let ((consistent t)) |
|---|
| 552 | (unless (every #'m2-points-to-contract (contract-m2s contract)) |
|---|
| 553 | (let ((*print-length* 5)) |
|---|
| 554 | (warn "~s of ~s dont point to it by M2-CONTRACT~ |
|---|
| 555 | ~%either those m2s are free or point to another contract~ |
|---|
| 556 | ~%the wrongly pointed to objs with duplicates removed are: ~s" |
|---|
| 557 | (remove-if #'m2-points-to-contract (contract-m2s contract)) |
|---|
| 558 | contract |
|---|
| 559 | (remove-duplicates (remove contract (mapcar #'m2-contract (contract-m2s contract)))))) |
|---|
| 560 | (setq consistent nil)) |
|---|
| 561 | (when (null (contract-m2s contract)) |
|---|
| 562 | (warn "~s has no m2s" contract) |
|---|
| 563 | (setq consistent nil)) |
|---|
| 564 | consistent))) |
|---|
| 565 | |
|---|
| 566 | (defvar *last-contracts-cache* nil) |
|---|
| 567 | (defconstant +last-contracts-cache-size+ 20) |
|---|
| 568 | |
|---|
| 569 | (defun last-paid-contracts () |
|---|
| 570 | (unless *last-contracts-cache* |
|---|
| 571 | (setf *last-contracts-cache* (subseq (append (sort (remove-if-not #'contract-paidp (class-instances 'contract)) |
|---|
| 572 | #'> :key #'contract-date) |
|---|
| 573 | (make-list +last-contracts-cache-size+)) |
|---|
| 574 | 0 +last-contracts-cache-size+))) |
|---|
| 575 | (remove-if #'object-destroyed-p *last-contracts-cache*)) |
|---|
| 576 | |
|---|
| 577 | (defun add-contract-to-cache (contract) |
|---|
| 578 | (last-paid-contracts) ; force cache initialization, should really be done by a eval-when |
|---|
| 579 | (push contract *last-contracts-cache*) |
|---|
| 580 | (setf (cdr (nthcdr (1- +last-contracts-cache-size+) *last-contracts-cache*)) nil)) |
|---|
| 581 | |
|---|
| 582 | (defun number-of-sold-sqm () |
|---|
| 583 | (let ((retval 0)) |
|---|
| 584 | (dolist (contract (remove-if-not #'contract-paidp (class-instances 'contract))) |
|---|
| 585 | (incf retval (length (contract-m2s contract)))) |
|---|
| 586 | retval)) |
|---|
| 587 | |
|---|
| 588 | (defun string-safe (string) |
|---|
| 589 | (if string |
|---|
| 590 | (escape-nl (arnesi:escape-as-html string)) |
|---|
| 591 | "")) |
|---|
| 592 | |
|---|
| 593 | (defun make-m2-javascript (sponsor) |
|---|
| 594 | "Erzeugt das Quadratmeter-Javascript fÃŒr die angegebenen Contracts" |
|---|
| 595 | (with-output-to-string (*standard-output*) |
|---|
| 596 | (let ((paid-contracts (remove nil (sponsor-contracts sponsor) :key #'contract-paidp))) |
|---|
| 597 | (format t "profil = {};~%") |
|---|
| 598 | (format t "profil.id = ~D;~%" (store-object-id sponsor)) |
|---|
| 599 | (format t "profil.name = ~S;~%" (string-safe (or (user-full-name sponsor) "[anonym]"))) |
|---|
| 600 | (format t "profil.country = ~S;~%" (or (sponsor-country sponsor) "[unbekannt]")) |
|---|
| 601 | (format t "profil.anzahl = ~D;~%" (loop for contract in paid-contracts |
|---|
| 602 | sum (length (contract-m2s contract)))) |
|---|
| 603 | (format t "profil.nachricht = '~A';~%" (string-safe (sponsor-info-text sponsor))) |
|---|
| 604 | (format t "profil.contracts = [ ];~%") |
|---|
| 605 | (loop for contract in paid-contracts |
|---|
| 606 | do (destructuring-bind (left top width height) (contract-bounding-box contract) |
|---|
| 607 | (format t "profil.contracts.push({ id: ~A, left: ~A, top: ~A, width: ~A, height: ~A, date: ~S });~%" |
|---|
| 608 | (store-object-id contract) |
|---|
| 609 | left top width height |
|---|
| 610 | (format-date-time (contract-date contract) :show-time nil))))))) |
|---|
| 611 | |
|---|
| 612 | (defun delete-directory (pathname) |
|---|
| 613 | (cl-fad:delete-directory-and-files pathname :if-does-not-exist :ignore)) |
|---|
| 614 | |
|---|
| 615 | (defun reinit (&key delete directory website-url enable-mails) |
|---|
| 616 | (format t "~&; Startup Quadratmeterdatenbank...~%") |
|---|
| 617 | (force-output) |
|---|
| 618 | (setf *enable-mails* enable-mails) |
|---|
| 619 | (setf *website-url* website-url) |
|---|
| 620 | (setf *rect-publisher* (make-rect-publisher)) |
|---|
| 621 | (unless directory |
|---|
| 622 | (error ":DIRECTORY parameter not set in m2.rc")) |
|---|
| 623 | (assert (and (null (pathname-name directory)) |
|---|
| 624 | (null (pathname-type directory))) |
|---|
| 625 | (directory) |
|---|
| 626 | ":DIRECTORY parameter is ~s (not a directory pathname)" directory) |
|---|
| 627 | (when delete |
|---|
| 628 | (delete-directory directory) |
|---|
| 629 | (assert (not (probe-file directory)))) |
|---|
| 630 | (close-store) |
|---|
| 631 | (make-instance 'm2-store |
|---|
| 632 | :directory directory |
|---|
| 633 | :subsystems (list (make-instance 'store-object-subsystem) |
|---|
| 634 | (make-instance 'blob-subsystem |
|---|
| 635 | :n-blobs-per-directory 1000) |
|---|
| 636 | (make-instance 'bos.m2.allocation-cache:allocation-cache-subsystem))) |
|---|
| 637 | (format t "~&; Startup der Quadratmeterdatenbank done.~%") |
|---|
| 638 | (force-output)) |
|---|
| 639 | |
|---|
| 640 | (defun consistent-p () |
|---|
| 641 | (let ((inconsistent-objs |
|---|
| 642 | (list |
|---|
| 643 | (remove-if #'sponsor-consistent-p (class-instances 'sponsor)) |
|---|
| 644 | (remove-if #'contract-consistent-p (class-instances 'contract)) |
|---|
| 645 | (remove-if #'allocation-area-consistent-p (class-instances 'allocation-area))))) |
|---|
| 646 | (values (every #'null inconsistent-objs) |
|---|
| 647 | inconsistent-objs))) |
|---|
| 648 | |
|---|
| 649 | ;; testing |
|---|
| 650 | |
|---|
| 651 | (defun fill-with-random-contracts (&optional percentage) |
|---|
| 652 | (loop for sponsor = (make-sponsor) |
|---|
| 653 | while (and (or (null percentage) |
|---|
| 654 | (< (allocation-area-percent-used (first (class-instances 'allocation-area))) percentage)) |
|---|
| 655 | (make-contract sponsor |
|---|
| 656 | (random-elt (cons (1+ (random 300)) |
|---|
| 657 | '(1 1 1 1 1 5 5 10 10 10 10 10 10 10 10 |
|---|
| 658 | 10 10 10 10 10 30 30 30))) |
|---|
| 659 | :paidp t)))) |
|---|
| 660 | |
|---|
| 661 | |
|---|
| 662 | ;;; for quick visualization |
|---|
| 663 | #+ltk |
|---|
| 664 | (defun show-m2s-polygon (m2s &aux (points (m2s-polygon m2s))) |
|---|
| 665 | (labels ((compute-bounding-box (m2s) |
|---|
| 666 | (let* ((left (m2-x (elt m2s 0))) |
|---|
| 667 | (top (m2-y (elt m2s 0))) |
|---|
| 668 | (right left) |
|---|
| 669 | (bottom top)) |
|---|
| 670 | (loop for i from 1 below (length m2s) do |
|---|
| 671 | (let* ((v (elt m2s i)) |
|---|
| 672 | (x (m2-x v)) |
|---|
| 673 | (y (m2-y v))) |
|---|
| 674 | (setf left (min left x) |
|---|
| 675 | right (max right x) |
|---|
| 676 | top (min top y) |
|---|
| 677 | bottom (max bottom y)))) |
|---|
| 678 | (values left top (- right left) (- bottom top))))) |
|---|
| 679 | (multiple-value-bind (left top width height) |
|---|
| 680 | (compute-bounding-box m2s) |
|---|
| 681 | (declare (ignore width height)) |
|---|
| 682 | (finish-output) |
|---|
| 683 | (flet ((transform-x (x) |
|---|
| 684 | (+ 30 (* 30 (- x left)))) |
|---|
| 685 | (transform-y (y) |
|---|
| 686 | (+ 30 (* 30 (- y top))))) |
|---|
| 687 | (ltk:with-ltk () |
|---|
| 688 | (let ((canvas (make-instance 'ltk:canvas :width 700 :height 700))) |
|---|
| 689 | ;; draw m2s |
|---|
| 690 | (loop for m2 in m2s |
|---|
| 691 | for x = (transform-x (m2-x m2)) |
|---|
| 692 | for y = (transform-y (m2-y m2)) |
|---|
| 693 | do (ltk:create-text canvas (+ 10 x) (+ 10 y) "x")) |
|---|
| 694 | ;; draw polygon |
|---|
| 695 | (loop for a in points |
|---|
| 696 | for b in (cdr points) |
|---|
| 697 | while (and a b) |
|---|
| 698 | do (ltk:create-line* canvas |
|---|
| 699 | (transform-x (first a)) (transform-y (second a)) |
|---|
| 700 | (transform-x (first b)) (transform-y (second b)))) |
|---|
| 701 | (let ((a (first points))) |
|---|
| 702 | (ltk:create-text canvas (transform-x (first a)) (transform-y (second a)) "o")) |
|---|
| 703 | (ltk:pack canvas))))))) |
|---|
| 704 | |
|---|
| 705 | #+ltk |
|---|
| 706 | (defun show-contract-center (contract) |
|---|
| 707 | (labels ((compute-bounding-box (m2s) |
|---|
| 708 | (let* ((left (m2-x (elt m2s 0))) |
|---|
| 709 | (top (m2-y (elt m2s 0))) |
|---|
| 710 | (right left) |
|---|
| 711 | (bottom top)) |
|---|
| 712 | (loop for i from 1 below (length m2s) do |
|---|
| 713 | (let* ((v (elt m2s i)) |
|---|
| 714 | (x (m2-x v)) |
|---|
| 715 | (y (m2-y v))) |
|---|
| 716 | (setf left (min left x) |
|---|
| 717 | right (max right x) |
|---|
| 718 | top (min top y) |
|---|
| 719 | bottom (max bottom y)))) |
|---|
| 720 | (values left top (- right left) (- bottom top))))) |
|---|
| 721 | (let* ((m2s (contract-m2s contract)) |
|---|
| 722 | (rectangle (contract-largest-rectangle contract)) |
|---|
| 723 | (center (geometry:rectangle-center rectangle))) |
|---|
| 724 | (multiple-value-bind (left top width height) |
|---|
| 725 | (compute-bounding-box m2s) |
|---|
| 726 | (declare (ignore width height)) |
|---|
| 727 | (finish-output) |
|---|
| 728 | (flet ((transform-x (x) |
|---|
| 729 | (+ 30 (* 30 (- x left)))) |
|---|
| 730 | (transform-y (y) |
|---|
| 731 | (+ 30 (* 30 (- y top))))) |
|---|
| 732 | (ltk:with-ltk () |
|---|
| 733 | (let ((canvas (make-instance 'ltk:canvas :width 700 :height 700))) |
|---|
| 734 | ;; draw m2s |
|---|
| 735 | (loop for m2 in m2s |
|---|
| 736 | for x = (transform-x (m2-x m2)) |
|---|
| 737 | for y = (transform-y (m2-y m2)) |
|---|
| 738 | do (ltk:create-text canvas (+ 10 x) (+ 10 y) "x")) |
|---|
| 739 | (geometry:with-rectangle rectangle |
|---|
| 740 | (ltk:create-rectangle canvas (transform-x left) (transform-y top) |
|---|
| 741 | (transform-x (+ left width)) (transform-y (+ top height)))) |
|---|
| 742 | (destructuring-bind (x y) |
|---|
| 743 | center |
|---|
| 744 | (geometry:with-rectangle ((list (- x 0.1) (- y 0.1) 0.2 0.2)) |
|---|
| 745 | (ltk:create-rectangle canvas (transform-x left) (transform-y top) |
|---|
| 746 | (transform-x (+ left width)) (transform-y (+ top height))))) |
|---|
| 747 | (ltk:pack canvas)))))))) |
|---|
| 748 | |
|---|