root/trunk/projects/bos/m2/m2.lisp @ 3009

Revision 2973, 27.2 KB (checked in by ksprotte, 2 years ago)

make-contract-tree-from-m2 is now more robust. it erred when there were no allocation-areas / no contracts

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
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
306and 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
521suitable allocation area was found.  Please check the free allocation
522areas and add more space.
523
524Sponsor-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
532that 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
541neighbours."
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
Note: See TracBrowser for help on using the browser.