root/trunk/projects/bos/m2/poi.lisp

Revision 4104, 14.4 kB (checked in by hans, 3 days ago)

Refactor sat-tree handler.
Move to new JSON object serialization API.
Experiment with satellite image in JS.

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 ;; poi.lisp
2
3 ;; Klassen und Funktione fÃŒr die "Points of Information", die fÃŒr die
4 ;; Quadratmeter-Datenbank gespeichert werden.
5
6 (in-package :bos.m2)
7
8 ;;; POI-Anwendungsklassen und Konstruktoren
9
10 ;;; textual-attributes-mixin
11 (defpersistent-class textual-attributes-mixin ()
12   ((title :initform (make-string-hash-table)
13           :documentation "angezeigter name")
14    (subtitle :initform (make-string-hash-table)
15              :documentation "unterschrift")
16    (description :initform (make-string-hash-table)
17                 :documentation "beschreibungstext")))
18
19 (defmethod initialize-instance :after ((obj textual-attributes-mixin)
20                                                   &key language title subtitle description)
21   (update-textual-attributes obj language
22                              :title title
23                              :subtitle subtitle
24                              :description description))
25
26 (deftransaction update-textual-attributes (obj language &key title subtitle description)
27   (when title
28     (setf (slot-string obj 'title language) title))
29   (when subtitle
30     (setf (slot-string obj 'subtitle language) subtitle))
31   (when description
32     (setf (slot-string obj 'description language) description))
33   obj)
34
35 (deftransaction very-shallow-copy-textual-attributes (from to)
36   "Useful for making the TEXTUAL-ATTRIBUTES of FROM available to TO,
37 before FROM is deleted. Please note that copying is so shallow that
38 FROM and TO must not both continue to exist."
39   (setf (slot-value to 'title) (slot-value from 'title)
40         (slot-value to 'subtitle) (slot-value from 'subtitle)
41         (slot-value to 'description) (slot-value from 'description))
42   to)
43
44 ;;; poi-medium
45 (defpersistent-class poi-medium (textual-attributes-mixin)
46   ((poi :reader poi-medium-poi :initarg :poi)))
47
48 (deftransaction make-poi-medium (class-name &rest rest &key language title subtitle description poi initargs)
49   (declare (ignore poi initargs))
50   (assert (if (or title subtitle description) language t) nil
51           "language needs to be specified, if any of title, subtitle
52            or description is given")
53   (apply #'make-instance class-name (remove-keys '(:initargs) rest)))
54
55 (defmethod initialize-instance :after ((poi-medium poi-medium) &key poi)
56   (when poi
57     (push poi-medium (poi-media poi))))
58
59 (defmethod print-object ((object poi-medium) stream)
60   (print-unreadable-object (object stream :type t :identity nil)
61     (format stream "~D" (store-object-id object))))
62
63 (defgeneric poi-medium-creation-time (medium)
64   (:method ((medium blob))
65     (blob-timestamp medium)))
66
67 (defmethod destroy-object :before ((poi-medium poi-medium))
68   (with-slots (poi) poi-medium
69     (when poi
70       (setf (poi-media poi) (remove poi-medium (poi-media poi))))))
71
72 ;;; poi-image
73 (defpersistent-class poi-image (store-image poi-medium)
74   ())
75
76 ;;; poi-airal
77 (defpersistent-class poi-airal (store-image poi-medium)
78   ())
79
80 ;;; poi-panorama
81 (defpersistent-class poi-panorama (store-image poi-medium)
82   ())
83
84 ;;; poi-movie
85 (defpersistent-class poi-movie (poi-medium)
86   ((url :accessor poi-movie-url :initarg :url :initform nil)
87    (created :initform (error "need :created initarg when creating poi-medium")
88             :initarg :created
89             :reader poi-medium-creation-time)))
90
91 ;;; poi
92 (defpersistent-class poi (textual-attributes-mixin)
93   ((name
94     :reader poi-name :initarg :name
95     :index-type string-unique-index
96     :index-reader find-poi :index-values all-pois
97     :documentation "symbolischer name")
98    (published-web
99     :accessor poi-published-web :initarg :published-web :initform nil
100     :documentation "wenn dieses flag nil ist, wird der poi auf der Website nicht angezeigt")
101    (published-earth
102     :accessor poi-published-earth :initarg :published-earth :initform nil
103     :documentation "wenn dieses flag nil ist, wird der poi in Google Earth nicht angezeigt")
104    (area
105     :accessor poi-area :initarg :area :initform nil
106     :documentation "polygon mit den poi-koordinaten")
107    (icon
108     :accessor poi-icon :initarg :icon :initform "palme"
109     :documentation "name des icons")
110    (media
111     :accessor poi-media :initarg :media :initform nil
112     :documentation "liste aller poi-medien, wie poi-image, poi-airal ...")
113    (lod-min
114     :accessor poi-lod-min :initarg :poi-lod-min :initform 600
115     :documentation "the lod minimum used in Google Earth")))
116
117
118 (defmethod convert-slot-value-while-restoring ((object poi) (slot-name (eql 'published))
119                                                published)
120   (setf (slot-value object 'published-web) published))
121
122 (deftransaction make-poi (name &rest rest &key area language title subtitle description)
123   (declare (ignore area))
124   (assert (if (or title subtitle description) language t) nil
125           "language needs to be specified, if any of title, subtitle
126            or description is given")
127   (apply #'make-instance 'poi :name name rest))
128
129 (defmethod destroy-object :before ((poi poi))
130   (mapc #'delete-object (poi-media poi)))
131
132 (deftransaction update-poi (poi &key published-web published-earth icon area lod-min)
133   (check-type published-web boolean)
134   (check-type published-earth boolean)
135   (check-type area list)
136   (setf (poi-published-web poi) published-web
137         (poi-published-earth poi) published-earth)
138   (when icon
139     (setf (poi-icon poi) icon))
140   (when area
141     (setf (poi-area poi) area))
142   (when lod-min
143     (setf (poi-lod-min poi) (abs lod-min)))
144   poi)
145
146 (defmethod poi-complete ((poi poi) language)
147   (and (every #'(lambda (slot-name) (slot-string poi slot-name language nil)) '(title subtitle description))
148        (poi-area poi)
149        (<= 6 (count-if (lambda (medium) (typep medium 'poi-image)) (poi-media poi)))
150        t))
151
152 (defmethod poi-center-x ((poi poi))
153   (first (poi-area poi)))
154
155 (defmethod poi-center-y ((poi poi))
156   (second (poi-area poi)))
157
158 (defun poi-center-lon-lat (poi)
159   (geo-utm:utm-x-y-to-lon-lat (+ +nw-utm-x+ (poi-center-x poi)) (- +nw-utm-y+ (poi-center-y poi)) +utm-zone+ t))
160
161 (defmethod (setf poi-media) :after (value (poi poi))
162   (setf (slot-value poi 'media) (sort (slot-value poi 'media) #'> :key #'poi-medium-creation-time)))
163
164 ;;; POI media are stored in one list - for convenience we provide
165 ;;; accessors by type. POI-IMAGES e.g. returns a list of all
166 ;;; POI-IMAGES in the same order as they appear in the media list. The
167 ;;; second value is a list of corresponding positions in that list.
168 (macrolet ((define-poi-medium-reader (name)
169              (let ((type (find-symbol (subseq (symbol-name name) 0 (1- (length (symbol-name name)))))))
170                (assert type)
171                `(defun ,name (poi)
172                   ;; this surely could be optimized
173                   (let ((media-of-type (remove-if-not (lambda (medium) (typep medium ',type)) (poi-media poi))))
174                     (values media-of-type
175                             (mapcar (lambda (medium) (position medium (poi-media poi))) media-of-type)))))))
176   (define-poi-medium-reader poi-images)
177   (define-poi-medium-reader poi-airals)
178   (define-poi-medium-reader poi-panoramas)
179   (define-poi-medium-reader poi-movies))
180
181 (defun poi-sat-images (poi)
182   "We use the 6 last (oldest) images of POI as images for the
183   satellite application."
184   (multiple-value-bind (images positions)
185       (poi-images poi)
186     (let* ((length (length images))
187            (start (max 0 (- length 6))))
188       (values (subseq images start length)
189               (subseq positions start length)))))
190
191 ;;; Provides for the shifting of images in the edit-poi handler.
192 ;;; Exchanges (nth index (poi-sat-images poi)) with
193 ;;; (nth (1+ index) (poi-sat-images poi)).
194 (deftransaction poi-sat-images-exchange-neighbours (poi index)
195   (check-type index (mod 6))
196   (multiple-value-bind (images positions)
197       (poi-images poi)
198     (declare (ignore images))
199     (let ((media-index-a (nth index positions))
200           (media-index-b (nth (mod (1+ index) 6) positions)))
201       (rotatef (nth media-index-a (poi-media poi))
202                (nth media-index-b (poi-media poi))))))
203
204 (defun make-poi-javascript (language)
205   "Erzeugt das POI-Javascript fÃŒr das Infosystem"
206   (with-output-to-string (*standard-output*)
207     (format t "var anzahlSponsoren = ~D;~%" (number-of-paying-sponsors))
208     (format t "var anzahlVerkauft = ~D;~%" (number-of-sold-sqm))
209     (format t "var pois = new Array;~%")
210     (dolist (poi (sort (remove-if #'(lambda (poi) (or (not (poi-complete poi language))
211                                                       (not (poi-published-web poi))))
212                                   (store-objects-with-class 'poi))
213                        #'(lambda (poi-1 poi-2) (string-lessp (slot-string poi-1 'title language) (slot-string poi-2 'title language)))))
214       (format t "
215 var poi = { id: ~S,
216             symbol: ~S,
217             icon: ~S,
218             name: ~S,
219             untertitel: ~S,
220             text: ~S,
221             x: ~D,
222             y: ~D,
223             thumbnail: ~D,
224             published_earth: ~:[false~;true~]
225 };
226 "
227               (store-object-id poi)
228               (poi-name poi)
229               (poi-icon poi)
230               (slot-string poi 'title language)
231               (slot-string poi 'subtitle language)
232               (escape-nl (slot-string poi 'description language))
233               (poi-center-x poi)
234               (poi-center-y poi)
235               (length (poi-sat-images poi))
236               (poi-published-earth poi))
237       (format t "poi.thumbnail = ~D;~%" (length (poi-sat-images poi)))
238       (when (poi-airals poi)
239
240         (format t "poi.luftbild = ~D;~%" (store-object-id (first (poi-airals poi)))))
241       (when (poi-panoramas poi)
242         (format t "poi.panoramas = [ ~{~D~^, ~} ];~%" (mapcar #'store-object-id (poi-panoramas poi))))
243       (when (poi-movies poi)
244         (format t "poi.movies = [ ~{~S~^, ~} ];~%"
245                 (mapcar #'(lambda (movie)
246                             (assert (stringp (poi-movie-url movie)) nil
247                                     "POI-MOVIE-URL of ~S is ~S, but should be a string"
248                                     movie (poi-movie-url movie))
249                             (poi-movie-url movie))
250                         (poi-movies poi))))
251       (loop for slot-name in '(title subtitle description)
252          for javascript-name in '("imageueberschrift" "imageuntertitel" "imagetext")
253          for slot-values = (mapcar (lambda (image)
254                                      (escape-nl (slot-string image slot-name language)))
255                                    (poi-sat-images poi))
256          when slot-values
257          do (format t "poi.~A = [ ~{~S~^, ~} ];~%" javascript-name slot-values))
258       (format t "pois.push(poi);~%"))
259     (dolist (allocation-area (remove-if (complement #'allocation-area-active-p) (class-instances 'allocation-area)))
260       (destructuring-bind (x y) (allocation-area-center allocation-area)
261         (format t "poi = [];~%")
262         (format t "poi['icon'] = ~S;~%" "sale")
263         (format t "poi['name'] = ~S;~%" "Zu Verkaufen")
264         (format t "poi['x'] = ~D;~%" x)
265         (format t "poi['y'] = ~D;~%" y)
266         (format t "poi['thumbnail'] = 0;~%")
267         (format t "pois.push(poi);~%")))))
268
269 ;;; poi schema evolution aids
270
271 (define-modify-macro appendf (&rest args) append)
272
273 (defmethod convert-slot-value-while-restoring ((poi poi) (slot-name (eql 'airals)) value) 
274   (unless (slot-boundp poi 'media) (setf (slot-value poi 'media) nil))
275   (appendf (slot-value poi 'media) (mapcar (lambda (obj) (change-class obj 'poi-airal :poi poi)) value)))
276
277 (defmethod convert-slot-value-while-restoring ((poi poi) (slot-name (eql 'images)) value)
278   (unless (slot-boundp poi 'media) (setf (slot-value poi 'media) nil))
279   (appendf (slot-value poi 'media) (mapcar (lambda (obj) (change-class obj 'poi-image :poi poi)) value)))
280
281 (defmethod convert-slot-value-while-restoring ((poi poi) (slot-name (eql 'movies)) value)
282   (unless (slot-boundp poi 'media) (setf (slot-value poi 'media) nil))
283   (appendf (slot-value poi 'media) (mapcar (lambda (url) `(poi-movie :url ,url :poi ,poi)) value)))
284
285 (defmethod convert-slot-value-while-restoring ((poi poi) (slot-name (eql 'panoramas)) value)
286   (unless (slot-boundp poi 'media) (setf (slot-value poi 'media) nil))
287   (appendf (slot-value poi 'media) (mapcar (lambda (obj) (change-class obj 'poi-panorama :poi poi)) value)))
288
289 (defun pois-sanity-check ()
290   (labels ((poi-sanity-check (poi)
291              (dolist (medium (poi-media poi))
292                (unless (eq poi (poi-medium-poi medium))
293                  (warn "~s does not point to ~s" medium poi)))
294              (dolist (movie (poi-movies poi))
295                (unless (stringp (poi-movie-url movie))
296                  (warn "~s has a url of ~s" movie (poi-movie-url movie))))))
297     (mapc #'poi-sanity-check (class-instances 'poi))
298     (values)))
299
300 (defvar *language* "en"
301   "Current language for JSON encoding")
302
303 (defmethod json:encode ((object symbol) &optional stream)
304   (json:encode (string-downcase (symbol-name object)) stream))
305
306 (defmethod json:encode-slots progn ((object store-object))
307   (json:encode-object-element "id" (store-object-id object)))
308
309 (defmethod json:encode-slots progn ((poi poi))
310   (json:encode-object-elements
311    "name" (poi-name poi)
312    "icon" (poi-icon poi)
313    "x" (poi-center-x poi)
314    "y" (poi-center-y poi))
315   (json:with-object-element ("media")
316     (json:with-array ()
317       (dolist (medium (poi-media poi))
318         (json:encode-object medium)))))
319
320 (defmethod json:encode-slots progn ((blob blob))
321   (json:encode-object-elements
322    "type" (blob-type blob)
323    "timestamp" (format-date-time (blob-timestamp blob) :mail-style t)))
324
325 (defmethod json:encode-slots progn ((image store-image))
326   (json:encode-object-elements
327    "name" (store-image-name image)
328    "width" (store-image-width image)
329    "height" (store-image-height image)))
330
331 (defmethod json:encode-slots progn ((object bos.m2::textual-attributes-mixin))
332   (dolist (field '(title subtitle description))
333     (let ((string (slot-string object field *language*)))
334       (unless (equal "" string)
335         (json:encode-object-element field string)))))
336
337 (defmethod json:encode-slots progn ((medium poi-medium))
338   (json:encode-object-element
339    "mediumType"
340    (cl-ppcre:regex-replace "^poi-" (string-downcase (class-name (class-of medium))) "")))
341
342 (defmethod json:encode-slots progn ((movie poi-movie))
343   (json:encode-object-elements
344    "url" (poi-movie-url movie)
345    "timestamp" (format-date-time (poi-medium-creation-time movie) :mail-style t)))
346
347 (defun poi-as-json (poi language)
348   (let ((*language* language))
349     (json:encode-object poi)))
350
351 (defun pois-as-json (language)
352   (json:with-array ()
353     (dolist (poi (class-instances 'poi))
354       (when (poi-complete poi language)
355         (poi-as-json poi language)))))
Note: See TracBrowser for help on using the browser.