| 1 |
(in-package :bos.web) |
|---|
| 2 |
|
|---|
| 3 |
(enable-interpol-syntax) |
|---|
| 4 |
|
|---|
| 5 |
(defclass allocation-area-handler (admin-only-handler edit-object-handler) |
|---|
| 6 |
()) |
|---|
| 7 |
|
|---|
| 8 |
(defmethod handle-object-form ((handler allocation-area-handler) action (allocation-area (eql nil))) |
|---|
| 9 |
(with-bos-cms-page (:title "Allocation Areas") |
|---|
| 10 |
(html |
|---|
| 11 |
(:h2 "Defined allocation areas") |
|---|
| 12 |
((:table :border "1") |
|---|
| 13 |
(:tr (:th "ID") |
|---|
| 14 |
(:th "active?") |
|---|
| 15 |
(:th "total") |
|---|
| 16 |
(:th "free") |
|---|
| 17 |
(:th "%used") |
|---|
| 18 |
(:th "Google Earth view")) |
|---|
| 19 |
(loop for allocation-area in (all-allocation-areas) |
|---|
| 20 |
do (html |
|---|
| 21 |
(:tr |
|---|
| 22 |
(:td (cmslink (format nil "allocation-area/~D" (store-object-id allocation-area)) |
|---|
| 23 |
(:princ-safe (store-object-id allocation-area)))) |
|---|
| 24 |
(:td (if (allocation-area-active-p allocation-area) (html "yes") (html "no"))) |
|---|
| 25 |
(:td (:princ-safe (allocation-area-total-m2s allocation-area))) |
|---|
| 26 |
(:td (:princ-safe (allocation-area-free-m2s allocation-area))) |
|---|
| 27 |
(:td (:princ-safe (round (allocation-area-percent-used allocation-area))) "%") |
|---|
| 28 |
(:td (cmslink (format nil "look-at-allocation-area/~D" (store-object-id allocation-area)) |
|---|
| 29 |
"fly to view")))))) |
|---|
| 30 |
(:p (cmslink "create-allocation-area" "Create new allocation area"))))) |
|---|
| 31 |
|
|---|
| 32 |
(defmethod handle-object-form ((handler allocation-area-handler) action allocation-area) |
|---|
| 33 |
(with-bos-cms-page (:title "Allocation Area") |
|---|
| 34 |
(with-slots (active-p left top width height) allocation-area |
|---|
| 35 |
(html |
|---|
| 36 |
((:table :border "1") |
|---|
| 37 |
(:tr |
|---|
| 38 |
(:td "id") |
|---|
| 39 |
(:td (:princ-safe (store-object-id allocation-area)))) |
|---|
| 40 |
(:tr |
|---|
| 41 |
(:td "active?") |
|---|
| 42 |
(:td (if active-p (html "yes") (html "no")))) |
|---|
| 43 |
(:tr |
|---|
| 44 |
(:td "usage") |
|---|
| 45 |
(:td (:princ-safe (round (allocation-area-percent-used allocation-area))) "%")) |
|---|
| 46 |
(:tr |
|---|
| 47 |
(:td "x") |
|---|
| 48 |
(:td (:princ-safe left))) |
|---|
| 49 |
(:tr |
|---|
| 50 |
(:td "y") |
|---|
| 51 |
(:td (:princ-safe top))) |
|---|
| 52 |
(:tr |
|---|
| 53 |
(:td "width") |
|---|
| 54 |
(:td (:princ-safe width))) |
|---|
| 55 |
(:tr |
|---|
| 56 |
(:td "height") |
|---|
| 57 |
(:td (:princ-safe height))) |
|---|
| 58 |
(:tr |
|---|
| 59 |
(:td "total number of sqms") |
|---|
| 60 |
(:td (:princ-safe (allocation-area-total-m2s allocation-area)))) |
|---|
| 61 |
(:tr |
|---|
| 62 |
(:td "number of free sqms") |
|---|
| 63 |
(:td (:princ-safe (allocation-area-free-m2s allocation-area)))) |
|---|
| 64 |
(:tr |
|---|
| 65 |
(:td "number of contracts") |
|---|
| 66 |
(:td (:princ-safe (length (allocation-area-contracts allocation-area)))))) |
|---|
| 67 |
(:p |
|---|
| 68 |
((:form :method "post") |
|---|
| 69 |
(submit-button "delete" "delete" :confirm "Really delete the allocation area?") |
|---|
| 70 |
(if active-p |
|---|
| 71 |
(submit-button "deactivate" "deactivate" :confirm "Really deactivate the allocation area?") |
|---|
| 72 |
(submit-button "activate" "activate" :confirm "Really activate the allocation area?")))) |
|---|
| 73 |
(:h2 "Allocation Graphics") |
|---|
| 74 |
((:table :cellspacing "0" :cellpadding "0" :border "0") |
|---|
| 75 |
(loop for y from (floor top 90) below (ceiling (+ top height) 90) |
|---|
| 76 |
do (html (:tr |
|---|
| 77 |
(loop for x from (floor left 90) below (ceiling (+ left width) 90) |
|---|
| 78 |
for tile-x = (* 90 x) |
|---|
| 79 |
for tile-y = (* 90 y) |
|---|
| 80 |
do (html (:td ((:a :href #?"/enlarge-overview/$(tile-x)/$(tile-y)") |
|---|
| 81 |
((:img :width "90" :height "90" :border "0" :src #?"/overview/$(tile-x)/$(tile-y)")))))))))))))) |
|---|
| 82 |
|
|---|
| 83 |
(defmethod handle-object-form ((handler allocation-area-handler) (action (eql :delete)) allocation-area) |
|---|
| 84 |
(delete-object allocation-area) |
|---|
| 85 |
(with-bos-cms-page (:title "Allocation area has been deleted") |
|---|
| 86 |
(:h2 "The allocation area has been deleted"))) |
|---|
| 87 |
|
|---|
| 88 |
(defmethod handle-object-form ((handler allocation-area-handler) (action (eql :activate)) allocation-area) |
|---|
| 89 |
(bos.m2::activate-allocation-area allocation-area) |
|---|
| 90 |
(with-bos-cms-page (:title "Allocation area has been activated") |
|---|
| 91 |
(:h2 "The allocation area has been activated"))) |
|---|
| 92 |
|
|---|
| 93 |
(defmethod handle-object-form ((handler allocation-area-handler) (action (eql :deactivate)) allocation-area) |
|---|
| 94 |
(bos.m2::deactivate-allocation-area allocation-area) |
|---|
| 95 |
(with-bos-cms-page (:title "Allocation area has been deactivated") |
|---|
| 96 |
(:h2 "The allocation area has been deactivated"))) |
|---|
| 97 |
|
|---|
| 98 |
(defclass create-allocation-area-handler (admin-only-handler form-handler) |
|---|
| 99 |
()) |
|---|
| 100 |
|
|---|
| 101 |
(defmethod handle-form ((handler create-allocation-area-handler) action) |
|---|
| 102 |
(with-query-params (x y left top) |
|---|
| 103 |
(cond |
|---|
| 104 |
((and x y left top) |
|---|
| 105 |
(destructuring-bind (x y left top) (mapcar #'parse-integer (list x y left top)) |
|---|
| 106 |
(if (or (some (complement #'plusp) (list x y left top)) |
|---|
| 107 |
(<= x left) |
|---|
| 108 |
(<= y top)) |
|---|
| 109 |
(with-bos-cms-page (:title "Invalid area selected") |
|---|
| 110 |
(:h2 "Choose upper left corner first, then lower-right corner")) |
|---|
| 111 |
(redirect (format nil "/allocation-area/~D" (store-object-id |
|---|
| 112 |
(make-allocation-rectangle left top (- x left) (- y top)))))))) |
|---|
| 113 |
((and x y) |
|---|
| 114 |
(redirect (format nil "/map-browser/~A/~A?heading=~A&chosen-url=~A&" |
|---|
| 115 |
x y |
|---|
| 116 |
(encode-urlencoded "Choose lower right point of allocation area") |
|---|
| 117 |
(encode-urlencoded (format nil "~A?left=~A&top=~A&" |
|---|
| 118 |
(hunchentoot:request-uri*) |
|---|
| 119 |
x y))))) |
|---|
| 120 |
(t |
|---|
| 121 |
(with-bos-cms-page (:title "Create allocation area") |
|---|
| 122 |
((:form :method "POST" :enctype "multipart/form-data")) |
|---|
| 123 |
((:table :border "0") |
|---|
| 124 |
(:tr ((:td :colspan "2") |
|---|
| 125 |
(:h2 "Create from list of UTM coordinates"))) |
|---|
| 126 |
(:tr (:td "File: ") (:td ((:input :type "file" :name "text-file" :value "*.txt")))) |
|---|
| 127 |
(:tr (:td (submit-button "upload" "upload"))) |
|---|
| 128 |
(:tr ((:td :colspan "2") |
|---|
| 129 |
(:h2 "Create by choosing rectangular area"))) |
|---|
| 130 |
(:tr (:td "Start-X") (:td (text-field "start-x" :value 0 :size 5))) |
|---|
| 131 |
(:tr (:td "Start-Y") (:td (text-field "start-y" :value 0 :size 5))) |
|---|
| 132 |
(:tr (:td (submit-button "rectangle" "rectangle"))))))))) |
|---|
| 133 |
|
|---|
| 134 |
(defmethod handle-form ((handler create-allocation-area-handler) (action (eql :rectangle))) |
|---|
| 135 |
(with-query-params (start-x start-y) |
|---|
| 136 |
(redirect (format nil "/map-browser/~A/~A?heading=~A&chosen-url=~A&" |
|---|
| 137 |
start-x start-y |
|---|
| 138 |
(encode-urlencoded "Choose upper left point of allocation area") |
|---|
| 139 |
(encode-urlencoded (format nil "~A?" (hunchentoot:request-uri*))))))) |
|---|
| 140 |
|
|---|
| 141 |
(defmethod handle-form ((handler create-allocation-area-handler) (action (eql :upload))) |
|---|
| 142 |
(let ((uploaded-text-file (request-uploaded-file "text-file"))) |
|---|
| 143 |
(cond |
|---|
| 144 |
((not uploaded-text-file) |
|---|
| 145 |
(with-bos-cms-page (:title "No Text file uploaded") |
|---|
| 146 |
(:h2 "File not uploaded") |
|---|
| 147 |
(:p "Please upload your text file containing the allocation polygon UTM coordinates"))) |
|---|
| 148 |
(t |
|---|
| 149 |
(with-bos-cms-page (:title #?"Importing allocation polygons from uploaded text file") |
|---|
| 150 |
(handler-case |
|---|
| 151 |
(let* ((vertices (polygon-from-text-file (upload-pathname uploaded-text-file))) |
|---|
| 152 |
(existing-area (find (coerce vertices 'list) |
|---|
| 153 |
(class-instances 'allocation-area) |
|---|
| 154 |
:key #'(lambda (area) (coerce (allocation-area-vertices area) 'list)) |
|---|
| 155 |
:test #'equal))) |
|---|
| 156 |
(if existing-area |
|---|
| 157 |
(html (:p (:h2 "Polygon already imported") |
|---|
| 158 |
"The polygon " (:princ-safe vertices) " has already been " |
|---|
| 159 |
"imported as " |
|---|
| 160 |
(cmslink (format nil "allocation-area/~D" (store-object-id existing-area)) |
|---|
| 161 |
"allocation area " (:princ-safe (store-object-id existing-area))))) |
|---|
| 162 |
(let ((allocation-area (make-allocation-area vertices))) |
|---|
| 163 |
(html (:p (:h2 "Successfully imported new allocation area") |
|---|
| 164 |
"The polygon " |
|---|
| 165 |
(cmslink (format nil "allocation-area/~D" (store-object-id allocation-area)) |
|---|
| 166 |
(:princ-safe (store-object-id allocation-area))) |
|---|
| 167 |
" has been successfully imported"))))) |
|---|
| 168 |
(error (e) |
|---|
| 169 |
(html |
|---|
| 170 |
(:h2 "Error reading the text file") |
|---|
| 171 |
(:p "Please make sure that the uploaded file only contains a simple path.") |
|---|
| 172 |
(:p "The error encountered is:") |
|---|
| 173 |
(:pre (:princ-safe e)))))))))) |
|---|
| 174 |
|
|---|
| 175 |
(defun ensure-line (file regex &key skip) |
|---|
| 176 |
(handler-case |
|---|
| 177 |
(loop for line = (read-line file) |
|---|
| 178 |
when (scan regex line) |
|---|
| 179 |
do (return-from ensure-line) |
|---|
| 180 |
when (not skip) |
|---|
| 181 |
do (error "expected ~A but read ~A from file ~A" regex line file)) |
|---|
| 182 |
(error (e) |
|---|
| 183 |
(error "error ~A on file ~A while waiting for ~A" e file regex)))) |
|---|
| 184 |
|
|---|
| 185 |
(defun ensure-float (x) |
|---|
| 186 |
(typecase x |
|---|
| 187 |
(float t) |
|---|
| 188 |
(integer t) |
|---|
| 189 |
(t (error "invalid number ~S" x)))) |
|---|
| 190 |
|
|---|
| 191 |
(defun scale-coordinate (name min x) |
|---|
| 192 |
(unless (and (>= x min) |
|---|
| 193 |
(<= x (+ min +width+))) |
|---|
| 194 |
(error "invalid ~A coordinate ~A (must be between ~A and ~A)" name x min (+ min +width+))) |
|---|
| 195 |
(round (- x min))) |
|---|
| 196 |
|
|---|
| 197 |
(defun parse-point (line) |
|---|
| 198 |
(let ((line (string-right-trim '(#\Return) line))) |
|---|
| 199 |
(unless (ppcre:scan line "^\\s*$") |
|---|
| 200 |
(destructuring-bind (x y) (read-from-string (format nil "(~A)" line)) |
|---|
| 201 |
(cons (scale-coordinate 'x +nw-utm-x+ x) |
|---|
| 202 |
(- +width+ (scale-coordinate 'y (- +nw-utm-y+ +width+) y))))))) |
|---|
| 203 |
|
|---|
| 204 |
(defun polygon-from-text-file (filename) |
|---|
| 205 |
(coerce (with-open-file (input-file filename) |
|---|
| 206 |
(loop |
|---|
| 207 |
with last-point |
|---|
| 208 |
for line-number from 1 |
|---|
| 209 |
for line = (read-line input-file nil) |
|---|
| 210 |
while line |
|---|
| 211 |
for point = (handler-case |
|---|
| 212 |
(parse-point line) |
|---|
| 213 |
(error (e) |
|---|
| 214 |
(error "Problem with text file in line ~A '~A': ~A in " line-number line e))) |
|---|
| 215 |
when (and point (not (equal point last-point))) |
|---|
| 216 |
collect (setq last-point point))) |
|---|
| 217 |
'vector)) |
|---|
| 218 |
|
|---|
| 219 |
(defun parse-illustrator-point (line) |
|---|
| 220 |
(destructuring-bind (x y type &rest foo) (split " " line) |
|---|
| 221 |
(declare (ignore foo)) |
|---|
| 222 |
(unless (scan #?r"^[lm]$" type) |
|---|
| 223 |
(html "Could not parse line from illustrator file:" |
|---|
| 224 |
(:pre (:princ-safe line)))) |
|---|
| 225 |
(cons (round (read-from-string x)) |
|---|
| 226 |
(round (- 10800 (read-from-string y)))))) |
|---|
| 227 |
|
|---|
| 228 |
(defun polygons-from-illustrator-file (filename) |
|---|
| 229 |
;; convert from mac line endings to dos line endings |
|---|
| 230 |
(with-open-file (input-file filename) |
|---|
| 231 |
(with-input-from-string (file (regex-replace-all #?r"\r" (read-line input-file) #?"\n")) |
|---|
| 232 |
(ensure-line file #?r"^%!PS-Adobe-2.0") |
|---|
| 233 |
(ensure-line file #?r"^%AI3_Cropmarks: 0 0 10800 10800" :skip t) |
|---|
| 234 |
(ensure-line file #?r"^%%Note:" :skip t) |
|---|
| 235 |
(let (polygons) |
|---|
| 236 |
(loop for polygon = (loop for line = (read-line file) |
|---|
| 237 |
until (scan #?r"^n" line) |
|---|
| 238 |
collect (parse-illustrator-point line)) |
|---|
| 239 |
do (when (equal (first polygon) |
|---|
| 240 |
(first (last polygon))) |
|---|
| 241 |
(setf polygon (cdr polygon))) |
|---|
| 242 |
do (push (coerce polygon 'vector) polygons) |
|---|
| 243 |
until (equal #\% (peek-char nil file))) |
|---|
| 244 |
(ensure-line file #?r"^%%EOF" :skip t) |
|---|
| 245 |
polygons)))) |
|---|