Skip to content

Commit f8ffaa8

Browse files
committed
CMR-11098: pr feedback
1 parent fb212f4 commit f8ffaa8

File tree

7 files changed

+14857
-147
lines changed

7 files changed

+14857
-147
lines changed

search-app/src/cmr/search/services/parameters/converters/geometry.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@
109109

110110
(defmethod geometry->condition "LineString"
111111
[geometry options]
112-
(let [shape (line->shape geometry)]
112+
(let [shape (line->shape geometry options)]
113113
(qm/->SpatialCondition shape)))
114114

115115
(defmethod geometry->condition "LinearRing"
116116
[geometry options]
117-
(let [shape (line->shape geometry)]
117+
(let [shape (line->shape geometry options)]
118118
(qm/->SpatialCondition shape)))

search-app/src/cmr/search/services/parameters/converters/shapefile.clj

Lines changed: 121 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,21 @@
4646

4747
(defn winding-opts
4848
"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.
5050
5151
Note: For GeoJSON, we only normalize hole winding. Boundary winding is not normalized
5252
because GeoJSON coordinates come from GeoTools already validated. For small local polygons
5353
(the typical cartesian use case), geodetic CCW and cartesian CCW have the same orientation,
5454
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))
6664

6765
(defn unzip-file
6866
"Unzip a file (of type File) into a temporary directory and return the directory path as a File"
@@ -195,24 +193,22 @@
195193

196194
(defn features->conditions
197195
"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))))
216212

217213
(defn error-if
218214
"Throw a service error with the given message if `f` applied to `item` is true.
@@ -226,106 +222,98 @@
226222

227223
(defn esri-shapefile->condition-vec
228224
"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"))))))
259253

260254
(defn geojson->conditions-vec
261255
"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"))))))
293285

294286
(defn kml->conditions-vec
295287
"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"))))))
316306

317307
(defn in-memory->conditions-vec
318308
"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)))
325313

326314
(defmulti shapefile->conditions
327315
"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."
329317
(fn [shapefile-info & _]
330318
(debug (format "SHAPEFILE FORMAT: %s" (:contenty-type shapefile-info)))
331319
(if (:in-memory shapefile-info)
@@ -334,42 +322,34 @@
334322

335323
;; ESRI shapefiles
336324
(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))))
342328

343329
;; GeoJSON
344330
(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))))
350334

351335
;; KML
352336
(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))))
358340

359341
;; Simplfied and stored in memory
360342
(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))))
366346

367347
(defmethod p/parameter->condition :shapefile
368348
[context _concept-type _param value _options]
369349
(if (enable-shapefile-parameter-flag)
370350
(let [;; Params are added to context as keywords after sanitization
371351
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))
375355
(errors/throw-service-error :bad-request "Searching by shapefile is not enabled")))

search-app/src/cmr/search/services/parameters/parameter_validation.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@
706706
:include-has-granules :has-granules :hierarchical-facets
707707
:include-highlights :all-revisions :has-opendap-url
708708
:simplify-shapefile :cloud-hosted :standard-product
709-
:include-non-operational])]
709+
:include-non-operational :force-cartesian])]
710710
(mapcat
711711
(fn [[param value]]
712712
(when-not (contains? #{"true" "false" "unset"} (when value (string/lower-case value)))

search-app/test/cmr/search/test/unit/services/parameter_converters/shapefile.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,23 @@
5454
(testing "empty file"
5555
(try
5656
(let [shp-file (io/file (io/resource "shapefiles/empty.zip"))]
57-
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file}))
57+
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file} {}))
5858
(catch Exception e
5959
(is (= "Shapefile has no features"
6060
(.getMessage e))))))
6161

6262
(testing "corrupt zip file"
6363
(try
6464
(let [shp-file (io/file (io/resource "shapefiles/corrupt_file.zip"))]
65-
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file}))
65+
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file} {}))
6666
(catch Exception e
6767
(is (= "Error while uncompressing zip file: invalid END header (bad central directory offset)"
6868
(.getMessage e))))))
6969

7070
(testing "missing zip file"
7171
(try
7272
(let [shp-file (io/file (io/resource "shapefiles/missing_shapefile_shp.zip"))]
73-
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file}))
73+
(shapefile/esri-shapefile->condition-vec {:tempfile shp-file} {}))
7474
(catch Exception e
7575
(is (= "Incomplete shapefile: missing .shp file"
7676
(.getMessage e)))))))

0 commit comments

Comments
 (0)