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