GeoTIFF.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. import {Enum} from "../Enum.js";
  2. var GeoTIFF = (function (exports) {
  3. 'use strict';
  4. const Endianness = new Enum({
  5. LITTLE: "II",
  6. BIG: "MM",
  7. });
  8. const Type = new Enum({
  9. BYTE: {value: 1, bytes: 1},
  10. ASCII: {value: 2, bytes: 1},
  11. SHORT: {value: 3, bytes: 2},
  12. LONG: {value: 4, bytes: 4},
  13. RATIONAL: {value: 5, bytes: 8},
  14. SBYTE: {value: 6, bytes: 1},
  15. UNDEFINED: {value: 7, bytes: 1},
  16. SSHORT: {value: 8, bytes: 2},
  17. SLONG: {value: 9, bytes: 4},
  18. SRATIONAL: {value: 10, bytes: 8},
  19. FLOAT: {value: 11, bytes: 4},
  20. DOUBLE: {value: 12, bytes: 8},
  21. });
  22. const Tag = new Enum({
  23. IMAGE_WIDTH: 256,
  24. IMAGE_HEIGHT: 257,
  25. BITS_PER_SAMPLE: 258,
  26. COMPRESSION: 259,
  27. PHOTOMETRIC_INTERPRETATION: 262,
  28. STRIP_OFFSETS: 273,
  29. ORIENTATION: 274,
  30. SAMPLES_PER_PIXEL: 277,
  31. ROWS_PER_STRIP: 278,
  32. STRIP_BYTE_COUNTS: 279,
  33. X_RESOLUTION: 282,
  34. Y_RESOLUTION: 283,
  35. PLANAR_CONFIGURATION: 284,
  36. RESOLUTION_UNIT: 296,
  37. SOFTWARE: 305,
  38. COLOR_MAP: 320,
  39. SAMPLE_FORMAT: 339,
  40. MODEL_PIXEL_SCALE: 33550, // [GeoTIFF] TYPE: double N: 3
  41. MODEL_TIEPOINT: 33922, // [GeoTIFF] TYPE: double N: 6 * NUM_TIEPOINTS
  42. GEO_KEY_DIRECTORY: 34735, // [GeoTIFF] TYPE: short N: >= 4
  43. GEO_DOUBLE_PARAMS: 34736, // [GeoTIFF] TYPE: short N: variable
  44. GEO_ASCII_PARAMS: 34737, // [GeoTIFF] TYPE: ascii N: variable
  45. });
  46. const typeMapping = new Map([
  47. [Type.BYTE, Uint8Array],
  48. [Type.ASCII, Uint8Array],
  49. [Type.SHORT, Uint16Array],
  50. [Type.LONG, Uint32Array],
  51. [Type.RATIONAL, Uint32Array],
  52. [Type.SBYTE, Int8Array],
  53. [Type.UNDEFINED, Uint8Array],
  54. [Type.SSHORT, Int16Array],
  55. [Type.SLONG, Int32Array],
  56. [Type.SRATIONAL, Int32Array],
  57. [Type.FLOAT, Float32Array],
  58. [Type.DOUBLE, Float64Array],
  59. ]);
  60. class IFDEntry{
  61. constructor(tag, type, count, offset, value){
  62. this.tag = tag;
  63. this.type = type;
  64. this.count = count;
  65. this.offset = offset;
  66. this.value = value;
  67. }
  68. }
  69. class Image{
  70. constructor(){
  71. this.width = 0;
  72. this.height = 0;
  73. this.buffer = null;
  74. this.metadata = [];
  75. }
  76. }
  77. class Reader{
  78. constructor(){
  79. }
  80. static read(data){
  81. let endiannessTag = String.fromCharCode(...Array.from(data.slice(0, 2)));
  82. let endianness = Endianness.fromValue(endiannessTag);
  83. let tiffCheckTag = data.readUInt8(2);
  84. if(tiffCheckTag !== 42){
  85. throw new Error("not a valid tiff file");
  86. }
  87. let offsetToFirstIFD = data.readUInt32LE(4);
  88. console.log("offsetToFirstIFD", offsetToFirstIFD);
  89. let ifds = [];
  90. let IFDsRead = false;
  91. let currentIFDOffset = offsetToFirstIFD;
  92. let i = 0;
  93. while(IFDsRead || i < 100){
  94. console.log("currentIFDOffset", currentIFDOffset);
  95. let numEntries = data.readUInt16LE(currentIFDOffset);
  96. let nextIFDOffset = data.readUInt32LE(currentIFDOffset + 2 + numEntries * 12);
  97. console.log("next offset: ", currentIFDOffset + 2 + numEntries * 12);
  98. let entryBuffer = data.slice(currentIFDOffset + 2, currentIFDOffset + 2 + 12 * numEntries);
  99. for(let i = 0; i < numEntries; i++){
  100. let tag = Tag.fromValue(entryBuffer.readUInt16LE(i * 12));
  101. let type = Type.fromValue(entryBuffer.readUInt16LE(i * 12 + 2));
  102. let count = entryBuffer.readUInt32LE(i * 12 + 4);
  103. let offsetOrValue = entryBuffer.readUInt32LE(i * 12 + 8);
  104. let valueBytes = type.bytes * count;
  105. let value;
  106. if(valueBytes <= 4){
  107. value = offsetOrValue;
  108. }else{
  109. let valueBuffer = new Uint8Array(valueBytes);
  110. valueBuffer.set(data.slice(offsetOrValue, offsetOrValue + valueBytes));
  111. let ArrayType = typeMapping.get(type);
  112. value = new ArrayType(valueBuffer.buffer);
  113. if(type === Type.ASCII){
  114. value = String.fromCharCode(...value);
  115. }
  116. }
  117. let ifd = new IFDEntry(tag, type, count, offsetOrValue, value);
  118. ifds.push(ifd);
  119. }
  120. console.log("nextIFDOffset", nextIFDOffset);
  121. if(nextIFDOffset === 0){
  122. break;
  123. }
  124. currentIFDOffset = nextIFDOffset;
  125. i++;
  126. }
  127. let ifdForTag = (tag) => {
  128. for(let entry of ifds){
  129. if(entry.tag === tag){
  130. return entry;
  131. }
  132. }
  133. return null;
  134. };
  135. let width = ifdForTag(Tag.IMAGE_WIDTH, ifds).value;
  136. let height = ifdForTag(Tag.IMAGE_HEIGHT, ifds).value;
  137. let compression = ifdForTag(Tag.COMPRESSION, ifds).value;
  138. let rowsPerStrip = ifdForTag(Tag.ROWS_PER_STRIP, ifds).value;
  139. let ifdStripOffsets = ifdForTag(Tag.STRIP_OFFSETS, ifds);
  140. let ifdStripByteCounts = ifdForTag(Tag.STRIP_BYTE_COUNTS, ifds);
  141. let numStrips = Math.ceil(height / rowsPerStrip);
  142. let stripByteCounts = [];
  143. for(let i = 0; i < ifdStripByteCounts.count; i++){
  144. let type = ifdStripByteCounts.type;
  145. let offset = ifdStripByteCounts.offset + i * type.bytes;
  146. let value;
  147. if(type === Type.SHORT){
  148. value = data.readUInt16LE(offset);
  149. }else if(type === Type.LONG){
  150. value = data.readUInt32LE(offset);
  151. }
  152. stripByteCounts.push(value);
  153. }
  154. let stripOffsets = [];
  155. for(let i = 0; i < ifdStripOffsets.count; i++){
  156. let type = ifdStripOffsets.type;
  157. let offset = ifdStripOffsets.offset + i * type.bytes;
  158. let value;
  159. if(type === Type.SHORT){
  160. value = data.readUInt16LE(offset);
  161. }else if(type === Type.LONG){
  162. value = data.readUInt32LE(offset);
  163. }
  164. stripOffsets.push(value);
  165. }
  166. let imageBuffer = new Uint8Array(width * height * 3);
  167. let linesProcessed = 0;
  168. for(let i = 0; i < numStrips; i++){
  169. let stripOffset = stripOffsets[i];
  170. let stripBytes = stripByteCounts[i];
  171. let stripData = data.slice(stripOffset, stripOffset + stripBytes);
  172. let lineBytes = width * 3;
  173. for(let y = 0; y < rowsPerStrip; y++){
  174. let line = stripData.slice(y * lineBytes, y * lineBytes + lineBytes);
  175. imageBuffer.set(line, linesProcessed * lineBytes);
  176. if(line.length === lineBytes){
  177. linesProcessed++;
  178. }else{
  179. break;
  180. }
  181. }
  182. }
  183. console.log(`width: ${width}`);
  184. console.log(`height: ${height}`);
  185. console.log(`numStrips: ${numStrips}`);
  186. console.log("stripByteCounts", stripByteCounts.join(", "));
  187. console.log("stripOffsets", stripOffsets.join(", "));
  188. let image = new Image();
  189. image.width = width;
  190. image.height = height;
  191. image.buffer = imageBuffer;
  192. image.metadata = ifds;
  193. return image;
  194. }
  195. }
  196. class Exporter{
  197. constructor(){
  198. }
  199. static toTiffBuffer(image, params = {}){
  200. let offsetToFirstIFD = 8;
  201. let headerBuffer = new Uint8Array([0x49, 0x49, 42, 0, offsetToFirstIFD, 0, 0, 0]);
  202. let [width, height] = [image.width, image.height];
  203. let ifds = [
  204. new IFDEntry(Tag.IMAGE_WIDTH, Type.SHORT, 1, null, width),
  205. new IFDEntry(Tag.IMAGE_HEIGHT, Type.SHORT, 1, null, height),
  206. new IFDEntry(Tag.BITS_PER_SAMPLE, Type.SHORT, 4, null, new Uint16Array([8, 8, 8, 8])),
  207. new IFDEntry(Tag.COMPRESSION, Type.SHORT, 1, null, 1),
  208. new IFDEntry(Tag.PHOTOMETRIC_INTERPRETATION, Type.SHORT, 1, null, 2),
  209. new IFDEntry(Tag.ORIENTATION, Type.SHORT, 1, null, 1),
  210. new IFDEntry(Tag.SAMPLES_PER_PIXEL, Type.SHORT, 1, null, 4),
  211. new IFDEntry(Tag.ROWS_PER_STRIP, Type.LONG, 1, null, height),
  212. new IFDEntry(Tag.STRIP_BYTE_COUNTS, Type.LONG, 1, null, width * height * 3),
  213. new IFDEntry(Tag.PLANAR_CONFIGURATION, Type.SHORT, 1, null, 1),
  214. new IFDEntry(Tag.RESOLUTION_UNIT, Type.SHORT, 1, null, 1),
  215. new IFDEntry(Tag.SOFTWARE, Type.ASCII, 6, null, "......"),
  216. new IFDEntry(Tag.STRIP_OFFSETS, Type.LONG, 1, null, null),
  217. new IFDEntry(Tag.X_RESOLUTION, Type.RATIONAL, 1, null, new Uint32Array([1, 1])),
  218. new IFDEntry(Tag.Y_RESOLUTION, Type.RATIONAL, 1, null, new Uint32Array([1, 1])),
  219. ];
  220. if(params.ifdEntries){
  221. ifds.push(...params.ifdEntries);
  222. }
  223. let valueOffset = offsetToFirstIFD + 2 + ifds.length * 12 + 4;
  224. // create 12 byte buffer for each ifd and variable length buffers for ifd values
  225. let ifdEntryBuffers = new Map();
  226. let ifdValueBuffers = new Map();
  227. for(let ifd of ifds){
  228. let entryBuffer = new ArrayBuffer(12);
  229. let entryView = new DataView(entryBuffer);
  230. let valueBytes = ifd.type.bytes * ifd.count;
  231. entryView.setUint16(0, ifd.tag.value, true);
  232. entryView.setUint16(2, ifd.type.value, true);
  233. entryView.setUint32(4, ifd.count, true);
  234. if(ifd.count === 1 && ifd.type.bytes <= 4){
  235. entryView.setUint32(8, ifd.value, true);
  236. }else{
  237. entryView.setUint32(8, valueOffset, true);
  238. let valueBuffer = new Uint8Array(ifd.count * ifd.type.bytes);
  239. if(ifd.type === Type.ASCII){
  240. valueBuffer.set(new Uint8Array(ifd.value.split("").map(c => c.charCodeAt(0))));
  241. }else{
  242. valueBuffer.set(new Uint8Array(ifd.value.buffer));
  243. }
  244. ifdValueBuffers.set(ifd.tag, valueBuffer);
  245. valueOffset = valueOffset + valueBuffer.byteLength;
  246. }
  247. ifdEntryBuffers.set(ifd.tag, entryBuffer);
  248. }
  249. let imageBufferOffset = valueOffset;
  250. new DataView(ifdEntryBuffers.get(Tag.STRIP_OFFSETS)).setUint32(8, imageBufferOffset, true);
  251. let concatBuffers = (buffers) => {
  252. let totalLength = buffers.reduce( (sum, buffer) => (sum + buffer.byteLength), 0);
  253. let merged = new Uint8Array(totalLength);
  254. let offset = 0;
  255. for(let buffer of buffers){
  256. merged.set(new Uint8Array(buffer), offset);
  257. offset += buffer.byteLength;
  258. }
  259. return merged;
  260. };
  261. let ifdBuffer = concatBuffers([
  262. new Uint16Array([ifds.length]),
  263. ...ifdEntryBuffers.values(),
  264. new Uint32Array([0])]);
  265. let ifdValueBuffer = concatBuffers([...ifdValueBuffers.values()]);
  266. let tiffBuffer = concatBuffers([
  267. headerBuffer,
  268. ifdBuffer,
  269. ifdValueBuffer,
  270. image.buffer
  271. ]);
  272. return {width: width, height: height, buffer: tiffBuffer};
  273. }
  274. }
  275. exports.Tag = Tag;
  276. exports.Type = Type;
  277. exports.IFDEntry = IFDEntry;
  278. exports.Image = Image;
  279. exports.Reader = Reader;
  280. exports.Exporter = Exporter;
  281. return exports;
  282. }({}));