11---
2- author : luke-zeitlin
2+ author :
3+ name : Luke Zeitlin
4+ url : https://github.com/larzeitlin
5+ image : https://github.com/larzeitlin.png
6+ affiliation : []
7+ image : tiff_geotiff_cog.png
38type : post
49date : ' 2025-12-07'
510category : gis
@@ -16,7 +21,7 @@ format:
1621.clay-side-by-side .sourceCode {margin: 0}
1722.clay-side-by-side {margin: 1em 0}
1823</style >
19- <script src =" geotiff_files/md-default5. js" type =" text/javascript " ></script ><script src =" geotiff_files/md-default6 .js" type =" text/javascript " ></script >
24+ <script src =" https://code.jquery.com/jquery-3.6.0.min. js" type =" text/javascript " ></script ><script src =" https://code.jquery.com/ui/1.13.1/jquery-ui.min .js" type =" text/javascript " ></script >
2025
2126# Cloud Optimized GeoTIFFs in JVM Clojure
2227
@@ -111,6 +116,14 @@ javax.imageio.stream.FileImageInputStream
111116
112117
113118
119+ ::: {.sourceClojure}
120+ ``` clojure
121+ (require '[tech.v3.datatype :as dtype])
122+ ```
123+ :::
124+
125+
126+
114127::: {.sourceClojure}
115128``` clojure
116129(defn get-pixel [^BufferedImage image x y]
@@ -120,7 +133,8 @@ javax.imageio.stream.FileImageInputStream
120133 num-bands (.getNumBands raster)
121134 pixel (double-array num-bands)]
122135 (.getPixel raster x y pixel)
123- (vec pixel)))
136+ ; ; A dtype-next read-only buffer as a view of the Java array:
137+ (dtype/as-reader pixel)))
124138```
125139:::
126140
@@ -150,7 +164,8 @@ Now we use the above helper functions to handle our locally held TIFF.
150164
151165::: {.printedClojure}
152166``` clojure
153- [45.0 ]
167+ #buffer<float64>[1 ]
168+ [45.00 ]
154169
155170```
156171:::
@@ -196,7 +211,8 @@ This also works for a GeoTIFF:
196211
197212::: {.printedClojure}
198213``` clojure
199- [42.0 76.0 32.0 ]
214+ #buffer<float64>[3 ]
215+ [42.00 , 76.00 , 32.00 ]
200216
201217```
202218:::
@@ -219,7 +235,7 @@ to work with.
219235
220236::: {.printedClojure}
221237``` clojure
222- #object[it.geosolutions. imageioimpl.plugins.tiff.TIFFImageMetadata 0x66719488 " it.geosolutions. imageioimpl.plugins.tiff.TIFFImageMetadata@66719488 " ]
238+ #object[com.sun.media. imageioimpl.plugins.tiff.TIFFImageMetadata 0x4b46954f " com.sun.media. imageioimpl.plugins.tiff.TIFFImageMetadata@4b46954f " ]
223239
224240```
225241:::
@@ -232,6 +248,7 @@ into formats handled by Clojure's dataset stack. Here I have done that via `Buff
232248There may well be a more convenient way - please let me know!
233249
234250There are several interconnected libraries for datasets in Clojure:
251+
235252- ` tablecloth ` -> high level API for interacting with ` tech.ml.dataset `
236253- ` tech.ml.dataset ` -> tabular datasets in Clojure. This is the core library.
237254- ` dtype-next ` -> the lowest level of this stack. array operations, buffer management, etc.
@@ -242,7 +259,9 @@ bring along the rest of the stack.
242259
243260::: {.sourceClojure}
244261``` clojure
245- (require '[tablecloth.api :as tc])
262+ (require '[tablecloth.api :as tc]
263+ '[tech.v3.tensor :as tensor]
264+ '[tech.v3.dataset.tensor :as ds-tensor])
246265```
247266:::
248267
@@ -253,26 +272,43 @@ bring along the rest of the stack.
253272(defn buffered-image->dataset [^BufferedImage img]
254273 (let [width (.getWidth img)
255274 height (.getHeight img)
256- raster (.getRaster img)
257- num-bands (.getNumBands raster)
258-
259- ; ; Pre-allocate arrays for each band's pixel data
260- band-arrays (vec (repeatedly num-bands #(double-array (* width height))))
261-
262- ; ; Read each band's data
263- _ (doseq [band (range num-bands)]
264- (.getSamples raster 0 0 width height band (nth band-arrays band)))
265-
266- ; ; Create coordinate arrays
267- xs (vec (for [_y (range height) x (range width)] x))
268- ys (vec (for [y (range height) _x (range width)] y))
275+ num-bands (.getNumBands (.getRaster img))
276+
277+ ; ; The following will be easier after dtype-next's
278+ ; ; [#133](https://github.com/cnuernber/dtype-next/issues/133)
279+ ; ; is resolved.
280+ tensor (-> img
281+ dtype/->array-buffer
282+ ; ; height x width x bands
283+ (tensor/construct-tensor (dims/dimensions
284+ [width height num-bands]))
285+ ; ; bands x height x width
286+ (tensor/transpose [2 0 1 ]))
287+
288+ xs (dtype/as-reader
289+ (tensor/compute-tensor [height width]
290+ (fn [y x] x)))
291+ ys (dtype/as-reader
292+ (tensor/compute-tensor [height width]
293+ (fn [y x] y)))
269294
270295 ; ; Build dataset
271- ds-map (into {:x xs :y ys}
272- (map-indexed
273- (fn [i arr] [(keyword (str " band-" i)) (vec arr)])
274- band-arrays))]
275- (tc/dataset ds-map)))
296+ ds-map (into {:x xs :y ys}
297+ (for [i (range num-bands)]
298+ [(keyword (str " band-" i))
299+ (dtype/as-reader
300+ (tensor i))]))]
301+ (-> tensor
302+ (tensor/reshape [(* height width)
303+ num-bands])
304+ ds-tensor/tensor->dataset
305+ (tc/add-columns {:x xs
306+ :y ys})
307+ (tc/select-columns (concat [:x :y ]
308+ (range num-bands)))
309+ (tc/rename-columns (into {}
310+ (for [i (range num-bands)]
311+ [i (keyword (str " band-" i))]))))))
276312```
277313:::
278314
@@ -298,15 +334,15 @@ bring along the rest of the stack.
298334
299335
300336::: {.clay-dataset}
301- _ unnamed: descriptive-stats [ 5 11] :
337+ : _ unnamed: descriptive-stats [ 5 11] :
302338
303- | : col-name | : datatype | : n-valid | : n-missing | : min | : mean | : max | : standard-deviation | : skew | : first | : last |
304- | -----------| -----------| ---------:| -----------:| -----:| --------------:| -------:| --------------------:| ----------------:| -------:| ------- :|
305- | : x | : int64 | 29989001 | 0 | 0.0 | 2999.00000000 | 5998.0 | 1731.76213725 | 0.00000000E+00 | 0.0 | 5998.0 |
306- | : y | : int64 | 29989001 | 0 | 0.0 | 2499.00000000 | 4998.0 | 1443.08699303 | -1.10958482E-17 | 0.0 | 4998.0 |
307- | : band-0 | : float64 | 29989001 | 0 | 0.0 | 68.21786911 | 255.0 | 60.51839822 | 1.33067394E +00 | 42.0 | 9.0 |
308- | : band-1 | : float64 | 29989001 | 0 | 0.0 | 88.75898750 | 255.0 | 38.37694129 | 1.53742117E +00 | 76.0 | 54.0 |
309- | : band-2 | : float64 | 29989001 | 0 | 0.0 | 73.80655098 | 255.0 | 41.20030938 | 1.29732261E +00 | 32.0 | 71.0 |
339+ | : col-name | : datatype | : n-valid | : n-missing | : min | : mean | : max | : standard-deviation | : skew | : first | : last |
340+ | -----------| -----------| ---------:| -----------:| -----:| --------------:| -------:| --------------------:| ----------------:| -------:| ------:|
341+ | : x | : int64 | 29989001 | 0 | 0.0 | 2999.00000000 | 5998.0 | 1731.76213725 | 0.00000000E+00 | 0 | 5998 |
342+ | : y | : int64 | 29989001 | 0 | 0.0 | 2499.00000000 | 4998.0 | 1443.08699303 | -1.10958482E-17 | 0 | 4998 |
343+ | : band-0 | : uint8 | 29989001 | 0 | 0.0 | 76.92727144 | 255.0 | 48.50581256 | 1.20979977E +00 | 42 | 72 |
344+ | : band-1 | : uint8 | 29989001 | 0 | 0.0 | 76.92790493 | 255.0 | 48.50363602 | 1.20979051E +00 | 33 | 73 |
345+ | : band-2 | : uint8 | 29989001 | 0 | 0.0 | 76.92823122 | 255.0 | 48.50667146 | 1.20996005E +00 | 38 | 71 |
310346
311347
312348:::
@@ -349,7 +385,6 @@ a multispectral image.
349385::: {.sourceClojure}
350386``` clojure
351387(require '[tech.v3.dataset :as ds]
352- '[tech.v3.datatype :as dtype]
353388 '[tech.v3.datatype.statistics :as dstats])
354389```
355390:::
@@ -406,35 +441,22 @@ java.awt.Color
406441::: {.sourceClojure}
407442``` clojure
408443(defn dataset->image
409- [dataset {:keys [r g b]}]
410- (let [xs (ds/column dataset :x )
411- ys (ds/column dataset :y )
412- rs (ds/column dataset r)
413- gs (ds/column dataset g)
414- bs (ds/column dataset b)
415-
416- max-x (-> xs last int)
417- min-x (-> xs first int)
418- max-y (-> ys last int)
419- min-y (-> ys first int)
420-
421- width (inc (- max-x min-x))
422- height (inc (- max-y min-y))
423-
424- ; ; Create the image
425- img (BufferedImage. width height BufferedImage/TYPE_INT_RGB)]
426-
427- ; ; Set pixels
428- (dotimes [i (ds/row-count dataset)]
429- (let [x (- (int (dtype/get-value xs i)) min-x)
430- y (- (int (dtype/get-value ys i)) min-y)
431- r (int (dtype/get-value rs i))
432- g (int (dtype/get-value gs i))
433- b (int (dtype/get-value bs i))
434- color (Color. r g b)]
435- (.setRGB img x y (.getRGB color))))
436-
437- img))
444+ [dataset {:as columns-mapping
445+ :keys [r g b]}]
446+ (let [height (->> dataset
447+ :y
448+ ((juxt dstats/max dstats/min))
449+ (apply -))
450+ width (->> dataset
451+ :x
452+ ((juxt dstats/max dstats/min))
453+ (apply -))]
454+ (-> columns-mapping
455+ (update-vals dataset)
456+ tc/dataset
457+ ds-tensor/dataset->tensor
458+ (tensor/reshape [height width 3 ])
459+ bufimg/tensor->image)))
438460```
439461:::
440462
@@ -472,8 +494,10 @@ such a way that makes it not straightforward
472494to read using only built-in Java libraries. This metadata contains useful information
473495such as coordinate system, location, number of channels and so on.
474496If we want to use it, we have a number of options:
497+
475498- Roll your own GeoTIFF metadata reader using ` javax.imageio.metadata ` and use
476499field accessors and walk the metadata tree to find the information that you want.
500+
477501- Apache SIS
478502- GeoTools
479503- Python Interop (with Rasterio)
@@ -488,6 +512,7 @@ GIS operations such as getting raster values at specific geographic
488512coordinates.
489513Here are [ developer docs] ( https://sis.apache.org/book/en/developer-guide.html ) for Apache SIS.
490514It's worth a scan to be aware of some terminology used in this library, for example:
515+
491516- [ Coverage] ( https://sis.apache.org/book/en/developer-guide.html#Coverage ) : a function that returns an attribute value for a given coordinate
492517(this includes raster images).
493518
@@ -545,7 +570,7 @@ We can view the metadata in this interesting output format that SIS provides.
545570::: {.callout-note}
546571## OUT
547572```
548- #object[org.apache.sis.metadata.iso.DefaultMetadata 0x4af9f150 Metadata
573+ #object[org.apache.sis.metadata.iso.DefaultMetadata 0x78245dd0 Metadata
549574 ├─Metadata identifier……………………………………………………………… example_geotiff_medium
550575 ├─Metadata standard (1 of 2)…………………………………………… Geographic Information — Metadata Part 1: Fundamentals
551576 │ ├─Edition…………………………………………………………………………………… ISO 19115-1:2014
@@ -817,7 +842,7 @@ View COG dimensions without downloading the full image
817842{:x [499980.0 609780.0 ],
818843 :y [1790220.0 1900020.0 ],
819844 :crs
820- #object[org.apache.sis.referencing.crs.DefaultProjectedCRS 0x70bbb286 "ProjectedCRS[\"WGS 84 / UTM zone 36N\",\n BaseGeogCRS[\"WGS 84\",\n Datum[\"World Geodetic System 1984\",\n Ellipsoid[\"WGS 1984\", 6378137.0, 298.257223563]],\n Unit[\"degree\", 0.017453292519943295]],\n Conversion[\"UTM zone 36N\",\n Method[\"Transverse Mercator\"],\n Parameter[\"Latitude of natural origin\", 0.0],\n Parameter[\"Longitude of natural origin\", 33.0],\n Parameter[\"Scale factor at natural origin\", 0.9996],\n Parameter[\"False easting\", 500000.0],\n Parameter[\"False northing\", 0.0]],\n CS[Cartesian, 2],\n Axis[\"Easting (E)\", east],\n Axis[\"Northing (N)\", north],\n Unit[\"metre\", 1],\n Id[\"EPSG\", 32636, \"9.9.1\", Citation[\"Subset of EPSG\"], URI[\"urn:ogc:def:crs:EPSG:9.9.1:32636\"]],\n Remark[\"Definitions from public sources. When a definition corresponds to an EPSG object (ignoring metadata), the EPSG code is provided as a reference where to find the complete definition.\"]]"]}
845+ #object[org.apache.sis.referencing.crs.DefaultProjectedCRS 0x37992194 "ProjectedCRS[\"WGS 84 / UTM zone 36N\",\n BaseGeogCRS[\"WGS 84\",\n Datum[\"World Geodetic System 1984\",\n Ellipsoid[\"WGS 1984\", 6378137.0, 298.257223563]],\n Unit[\"degree\", 0.017453292519943295]],\n Conversion[\"UTM zone 36N\",\n Method[\"Transverse Mercator\"],\n Parameter[\"Latitude of natural origin\", 0.0],\n Parameter[\"Longitude of natural origin\", 33.0],\n Parameter[\"Scale factor at natural origin\", 0.9996],\n Parameter[\"False easting\", 500000.0],\n Parameter[\"False northing\", 0.0]],\n CS[Cartesian, 2],\n Axis[\"Easting (E)\", east],\n Axis[\"Northing (N)\", north],\n Unit[\"metre\", 1],\n Id[\"EPSG\", 32636, \"9.9.1\", Citation[\"Subset of EPSG\"], URI[\"urn:ogc:def:crs:EPSG:9.9.1:32636\"]],\n Remark[\"Definitions from public sources. When a definition corresponds to an EPSG object (ignoring metadata), the EPSG code is provided as a reference where to find the complete definition.\"]]"]}
821846
822847```
823848:::
@@ -975,4 +1000,4 @@ Pull requests are welcome. Please refer to `src/gis/geotiff.clj` at
9751000
9761001```{=html}
9771002<div><pre><small><small>source: <a href=" https://github.com/ClojureCivitas/clojurecivitas.github.io/blob/main/src/gis/geotiff.clj" >src/gis/geotiff.clj</a></small></small></pre></div>
978- ```
1003+ ```
0 commit comments