|
46 | 46 |
|
47 | 47 | (defn winding-opts |
48 | 48 | "Get the opts for a call to `normalize-polygon-winding` based on file type. |
49 | | - Optionally merges in force-cartesian option if provided in context. |
| 49 | + Optionally merges in force-cartesian option if provided in options. |
50 | 50 |
|
51 | 51 | Note: For GeoJSON, we only normalize hole winding. Boundary winding is not normalized |
52 | 52 | because GeoJSON coordinates come from GeoTools already validated. For small local polygons |
53 | 53 | (the typical cartesian use case), geodetic CCW and cartesian CCW have the same orientation, |
54 | 54 | so no boundary reversal is needed." |
55 | | - ([mime-type] |
56 | | - (winding-opts mime-type nil)) |
57 | | - ([mime-type context] |
58 | | - (let [base-opts (case mime-type |
59 | | - "application/shapefile+zip" {:boundary-winding :cw} |
60 | | - "application/vnd.google-earth.kml+xml" {} |
61 | | - "application/geo+json" {:hole-winding :cw}) |
62 | | - final-opts (if (:force-cartesian context) |
63 | | - (assoc base-opts :force-cartesian true) |
64 | | - base-opts)] |
65 | | - final-opts))) |
| 55 | + [mime-type options] |
| 56 | + (let [base-opts (case mime-type |
| 57 | + "application/shapefile+zip" {:boundary-winding :cw} |
| 58 | + "application/vnd.google-earth.kml+xml" {} |
| 59 | + "application/geo+json" {:hole-winding :cw}) |
| 60 | + final-opts (if (:force-cartesian options) |
| 61 | + (assoc base-opts :force-cartesian true) |
| 62 | + base-opts)] |
| 63 | + final-opts)) |
66 | 64 |
|
67 | 65 | (defn unzip-file |
68 | 66 | "Unzip a file (of type File) into a temporary directory and return the directory path as a File" |
|
195 | 193 |
|
196 | 194 | (defn features->conditions |
197 | 195 | "Converts a list of features into a vector of SpatialConditions. |
198 | | - The context map can contain force-cartesian setting which will be merged into the options." |
199 | | - ([features mime-type] |
200 | | - (features->conditions features mime-type nil)) |
201 | | - ([features mime-type context] |
202 | | - (validate-features features) |
203 | | - (let [iterator (.iterator features) |
204 | | - options (winding-opts mime-type context)] |
205 | | - ;; Loop overall all the Features in the list, building up a vector of conditions |
206 | | - (loop [conditions []] |
207 | | - (if (.hasNext iterator) |
208 | | - (let [feature (.next iterator) |
209 | | - [feature-conditions _] (feature->conditions feature options)] |
210 | | - (if (> (count feature-conditions) 0) |
211 | | - ;; if any conditions were created for the feature add them to the current conditions |
212 | | - (recur (conj conditions (gc/or-conds feature-conditions))) |
213 | | - (recur conditions))) |
214 | | - ;; no more Features in list - return conditions created |
215 | | - conditions))))) |
| 196 | + The options map can contain force-cartesian setting which will be merged into the winding options." |
| 197 | + [features mime-type options] |
| 198 | + (validate-features features) |
| 199 | + (let [iterator (.iterator features) |
| 200 | + winding-options (winding-opts mime-type options)] |
| 201 | + ;; Loop overall all the Features in the list, building up a vector of conditions |
| 202 | + (loop [conditions []] |
| 203 | + (if (.hasNext iterator) |
| 204 | + (let [feature (.next iterator) |
| 205 | + [feature-conditions _] (feature->conditions feature winding-options)] |
| 206 | + (if (> (count feature-conditions) 0) |
| 207 | + ;; if any conditions were created for the feature add them to the current conditions |
| 208 | + (recur (conj conditions (gc/or-conds feature-conditions))) |
| 209 | + (recur conditions))) |
| 210 | + ;; no more Features in list - return conditions created |
| 211 | + conditions)))) |
216 | 212 |
|
217 | 213 | (defn error-if |
218 | 214 | "Throw a service error with the given message if `f` applied to `item` is true. |
|
226 | 222 |
|
227 | 223 | (defn esri-shapefile->condition-vec |
228 | 224 | "Converts a shapefile to a vector of SpatialConditions" |
229 | | - ([shapefile-info] |
230 | | - (esri-shapefile->condition-vec shapefile-info nil)) |
231 | | - ([shapefile-info context] |
232 | | - (try |
233 | | - (let [file (:tempfile shapefile-info) |
234 | | - ^File temp-dir (unzip-file file) |
235 | | - shp-file (error-if |
236 | | - (find-shp-file temp-dir) |
237 | | - nil? |
238 | | - "Incomplete shapefile: missing .shp file" |
239 | | - temp-dir) |
240 | | - data-store (FileDataStoreFinder/getDataStore shp-file) |
241 | | - feature-source (.getFeatureSource data-store) |
242 | | - features (.getFeatures feature-source) |
243 | | - iterator (.features features) |
244 | | - feature-list (ArrayList.)] |
245 | | - (try |
246 | | - (while (.hasNext iterator) |
247 | | - (let [feature (.next iterator)] |
248 | | - (.add feature-list feature))) |
249 | | - (features->conditions feature-list mt/shapefile context) |
250 | | - (finally |
251 | | - (.close iterator) |
252 | | - (-> data-store .getFeatureReader .close) |
253 | | - (.delete temp-dir)))) |
254 | | - (catch Exception e |
255 | | - (let [{:keys [type errors]} (ex-data e)] |
256 | | - (if (and type errors) |
257 | | - (throw e) ;; This was a more specific service error so just re-throw it |
258 | | - (errors/throw-service-error :bad-request "Failed to parse shapefile"))))))) |
| 225 | + [shapefile-info options] |
| 226 | + (try |
| 227 | + (let [file (:tempfile shapefile-info) |
| 228 | + ^File temp-dir (unzip-file file) |
| 229 | + shp-file (error-if |
| 230 | + (find-shp-file temp-dir) |
| 231 | + nil? |
| 232 | + "Incomplete shapefile: missing .shp file" |
| 233 | + temp-dir) |
| 234 | + data-store (FileDataStoreFinder/getDataStore shp-file) |
| 235 | + feature-source (.getFeatureSource data-store) |
| 236 | + features (.getFeatures feature-source) |
| 237 | + iterator (.features features) |
| 238 | + feature-list (ArrayList.)] |
| 239 | + (try |
| 240 | + (while (.hasNext iterator) |
| 241 | + (let [feature (.next iterator)] |
| 242 | + (.add feature-list feature))) |
| 243 | + (features->conditions feature-list mt/shapefile options) |
| 244 | + (finally |
| 245 | + (.close iterator) |
| 246 | + (-> data-store .getFeatureReader .close) |
| 247 | + (.delete temp-dir)))) |
| 248 | + (catch Exception e |
| 249 | + (let [{:keys [type errors]} (ex-data e)] |
| 250 | + (if (and type errors) |
| 251 | + (throw e) ;; This was a more specific service error so just re-throw it |
| 252 | + (errors/throw-service-error :bad-request "Failed to parse shapefile")))))) |
259 | 253 |
|
260 | 254 | (defn geojson->conditions-vec |
261 | 255 | "Converts a geojson file to a vector of SpatialConditions" |
262 | | - ([shapefile-info] |
263 | | - (geojson->conditions-vec shapefile-info nil)) |
264 | | - ([shapefile-info context] |
265 | | - (try |
266 | | - (let [file (:tempfile shapefile-info) |
267 | | - _ (geojson/sanitize-geojson file) |
268 | | - url (URLs/fileToUrl file) |
269 | | - data-store (GeoJSONDataStore. url) |
270 | | - feature-source (.getFeatureSource data-store) |
271 | | - features (.getFeatures feature-source) |
272 | | - ;; Fail fast |
273 | | - _ (when (or (nil? features) |
274 | | - (nil? (.getSchema features)) |
275 | | - (.isEmpty features)) |
276 | | - (errors/throw-service-error :bad-request "Shapefile has no features")) |
277 | | - iterator (.features features) |
278 | | - feature-list (ArrayList.)] |
279 | | - (try |
280 | | - (while (.hasNext iterator) |
281 | | - (let [feature (.next iterator)] |
282 | | - (.add feature-list feature))) |
283 | | - (features->conditions feature-list mt/geojson context) |
284 | | - (finally |
285 | | - (.close iterator) |
286 | | - (-> data-store .getFeatureReader .close) |
287 | | - (.delete file)))) |
288 | | - (catch Exception e |
289 | | - (let [{:keys [type errors]} (ex-data e)] |
290 | | - (if (and type errors) |
291 | | - (throw e) ;; This was a more specific service error so just re-throw it |
292 | | - (errors/throw-service-error :bad-request "Failed to parse shapefile"))))))) |
| 256 | + [shapefile-info options] |
| 257 | + (try |
| 258 | + (let [file (:tempfile shapefile-info) |
| 259 | + _ (geojson/sanitize-geojson file) |
| 260 | + url (URLs/fileToUrl file) |
| 261 | + data-store (GeoJSONDataStore. url) |
| 262 | + feature-source (.getFeatureSource data-store) |
| 263 | + features (.getFeatures feature-source) |
| 264 | + ;; Fail fast |
| 265 | + _ (when (or (nil? features) |
| 266 | + (nil? (.getSchema features)) |
| 267 | + (.isEmpty features)) |
| 268 | + (errors/throw-service-error :bad-request "Shapefile has no features")) |
| 269 | + iterator (.features features) |
| 270 | + feature-list (ArrayList.)] |
| 271 | + (try |
| 272 | + (while (.hasNext iterator) |
| 273 | + (let [feature (.next iterator)] |
| 274 | + (.add feature-list feature))) |
| 275 | + (features->conditions feature-list mt/geojson options) |
| 276 | + (finally |
| 277 | + (.close iterator) |
| 278 | + (-> data-store .getFeatureReader .close) |
| 279 | + (.delete file)))) |
| 280 | + (catch Exception e |
| 281 | + (let [{:keys [type errors]} (ex-data e)] |
| 282 | + (if (and type errors) |
| 283 | + (throw e) ;; This was a more specific service error so just re-throw it |
| 284 | + (errors/throw-service-error :bad-request "Failed to parse shapefile")))))) |
293 | 285 |
|
294 | 286 | (defn kml->conditions-vec |
295 | 287 | "Converts a kml file to a vector of SpatialConditions" |
296 | | - ([shapefile-info] |
297 | | - (kml->conditions-vec shapefile-info nil)) |
298 | | - ([shapefile-info context] |
299 | | - (try |
300 | | - (let [file (:tempfile shapefile-info) |
301 | | - input-stream (FileInputStream. file) |
302 | | - parser (PullParser. (KMLConfiguration.) input-stream SimpleFeature) |
303 | | - feature-list (ArrayList.)] |
304 | | - (try |
305 | | - (util/while-let [feature (.parse parser)] |
306 | | - (when (> (feature-point-count feature) 0) |
307 | | - (.add feature-list feature))) |
308 | | - (features->conditions feature-list mt/kml context) |
309 | | - (finally |
310 | | - (.delete file)))) |
311 | | - (catch Exception e |
312 | | - (let [{:keys [type errors]} (ex-data e)] |
313 | | - (if (and type errors) |
314 | | - (throw e) ;; This was a more specific service error so just re-throw it |
315 | | - (errors/throw-service-error :bad-request "Failed to parse shapefile"))))))) |
| 288 | + [shapefile-info options] |
| 289 | + (try |
| 290 | + (let [file (:tempfile shapefile-info) |
| 291 | + input-stream (FileInputStream. file) |
| 292 | + parser (PullParser. (KMLConfiguration.) input-stream SimpleFeature) |
| 293 | + feature-list (ArrayList.)] |
| 294 | + (try |
| 295 | + (util/while-let [feature (.parse parser)] |
| 296 | + (when (> (feature-point-count feature) 0) |
| 297 | + (.add feature-list feature))) |
| 298 | + (features->conditions feature-list mt/kml options) |
| 299 | + (finally |
| 300 | + (.delete file)))) |
| 301 | + (catch Exception e |
| 302 | + (let [{:keys [type errors]} (ex-data e)] |
| 303 | + (if (and type errors) |
| 304 | + (throw e) ;; This was a more specific service error so just re-throw it |
| 305 | + (errors/throw-service-error :bad-request "Failed to parse shapefile")))))) |
316 | 306 |
|
317 | 307 | (defn in-memory->conditions-vec |
318 | 308 | "Converts a group of features produced by simplification to a vector of SpatialConditions" |
319 | | - ([shapefile-info] |
320 | | - (in-memory->conditions-vec shapefile-info nil)) |
321 | | - ([shapefile-info context] |
322 | | - (let [^ArrayList features (:tempfile shapefile-info) |
323 | | - mime-type (:content-type shapefile-info)] |
324 | | - (features->conditions features mime-type context)))) |
| 309 | + [shapefile-info options] |
| 310 | + (let [^ArrayList features (:tempfile shapefile-info) |
| 311 | + mime-type (:content-type shapefile-info)] |
| 312 | + (features->conditions features mime-type options))) |
325 | 313 |
|
326 | 314 | (defmulti shapefile->conditions |
327 | 315 | "Converts a shapefile to query conditions based on shapefile format. |
328 | | - Optionally accepts a context map containing force-cartesian setting." |
| 316 | + Optionally accepts an options map containing force-cartesian setting." |
329 | 317 | (fn [shapefile-info & _] |
330 | 318 | (debug (format "SHAPEFILE FORMAT: %s" (:contenty-type shapefile-info))) |
331 | 319 | (if (:in-memory shapefile-info) |
|
334 | 322 |
|
335 | 323 | ;; ESRI shapefiles |
336 | 324 | (defmethod shapefile->conditions mt/shapefile |
337 | | - ([shapefile-info] |
338 | | - (shapefile->conditions shapefile-info nil)) |
339 | | - ([shapefile-info context] |
340 | | - (let [conditions-vec (esri-shapefile->condition-vec shapefile-info context)] |
341 | | - (gc/or-conds (flatten conditions-vec))))) |
| 325 | + [shapefile-info options] |
| 326 | + (let [conditions-vec (esri-shapefile->condition-vec shapefile-info options)] |
| 327 | + (gc/or-conds (flatten conditions-vec)))) |
342 | 328 |
|
343 | 329 | ;; GeoJSON |
344 | 330 | (defmethod shapefile->conditions mt/geojson |
345 | | - ([shapefile-info] |
346 | | - (shapefile->conditions shapefile-info nil)) |
347 | | - ([shapefile-info context] |
348 | | - (let [conditions-vec (geojson->conditions-vec shapefile-info context)] |
349 | | - (gc/or-conds (flatten conditions-vec))))) |
| 331 | + [shapefile-info options] |
| 332 | + (let [conditions-vec (geojson->conditions-vec shapefile-info options)] |
| 333 | + (gc/or-conds (flatten conditions-vec)))) |
350 | 334 |
|
351 | 335 | ;; KML |
352 | 336 | (defmethod shapefile->conditions mt/kml |
353 | | - ([shapefile-info] |
354 | | - (shapefile->conditions shapefile-info nil)) |
355 | | - ([shapefile-info context] |
356 | | - (let [conditions-vec (kml->conditions-vec shapefile-info context)] |
357 | | - (gc/or-conds (flatten conditions-vec))))) |
| 337 | + [shapefile-info options] |
| 338 | + (let [conditions-vec (kml->conditions-vec shapefile-info options)] |
| 339 | + (gc/or-conds (flatten conditions-vec)))) |
358 | 340 |
|
359 | 341 | ;; Simplfied and stored in memory |
360 | 342 | (defmethod shapefile->conditions :in-memory |
361 | | - ([shapefile-info] |
362 | | - (shapefile->conditions shapefile-info nil)) |
363 | | - ([shapefile-info context] |
364 | | - (let [conditions-vec (in-memory->conditions-vec shapefile-info context)] |
365 | | - (gc/or-conds (flatten conditions-vec))))) |
| 343 | + [shapefile-info options] |
| 344 | + (let [conditions-vec (in-memory->conditions-vec shapefile-info options)] |
| 345 | + (gc/or-conds (flatten conditions-vec)))) |
366 | 346 |
|
367 | 347 | (defmethod p/parameter->condition :shapefile |
368 | 348 | [context _concept-type _param value _options] |
369 | 349 | (if (enable-shapefile-parameter-flag) |
370 | 350 | (let [;; Params are added to context as keywords after sanitization |
371 | 351 | force-cartesian-value (get-in context [:params :force-cartesian]) |
372 | | - force-cartesian (or (= "true" force-cartesian-value) (= true force-cartesian-value)) |
373 | | - shapefile-context (when force-cartesian {:force-cartesian true})] |
374 | | - (shapefile->conditions value shapefile-context)) |
| 352 | + force-cartesian (= "true" (util/safe-lowercase force-cartesian-value)) |
| 353 | + options (when force-cartesian {:force-cartesian true})] |
| 354 | + (shapefile->conditions value options)) |
375 | 355 | (errors/throw-service-error :bad-request "Searching by shapefile is not enabled"))) |
0 commit comments