diff --git a/src/geotiffwriter.js b/src/geotiffwriter.js index 9f542b0e..0030373d 100644 --- a/src/geotiffwriter.js +++ b/src/geotiffwriter.js @@ -432,50 +432,79 @@ export function writeGeotiff(data, metadata) { .filter((key) => endsWith(key, 'GeoKey')) .sort((a, b) => name2code[a] - name2code[b]); - if (!metadata.GeoAsciiParams) { - let geoAsciiParams = ''; - geoKeys.forEach((name) => { - const code = Number(name2code[name]); - const tagType = fieldTagTypes[code]; - if (tagType === 'ASCII') { - geoAsciiParams += `${metadata[name].toString()}\u0000`; - } - }); - if (geoAsciiParams.length > 0) { - metadata.GeoAsciiParams = geoAsciiParams; - } - } - + // If not provided, build GeoKeyDirectory as well as GeoAsciiParamsTag and GeoDoubleParamsTag + // if GeoAsciiParams/GeoDoubleParams were passed in, we assume offsets are already correct + // Spec http://geotiff.maptools.org/spec/geotiff2.4.html if (!metadata.GeoKeyDirectory) { - const NumberOfKeys = geoKeys.length; - - const GeoKeyDirectory = [1, 1, 0, NumberOfKeys]; + // Only build ASCII / DOUBLE params if not provided + let geoAsciiParams = metadata.GeoAsciiParams || ''; + let currentAsciiOffset = geoAsciiParams.length; + const geoDoubleParams = metadata.GeoDoubleParams || []; + let currentDoubleIndex = geoDoubleParams.length; + + // Since geoKeys already sorted and filtered, do a single pass to append to corresponding directory for SHORT/ASCII/DOUBLE + const GeoKeyDirectory = [1, 1, 0, 0]; + let validKeys = 0; geoKeys.forEach((geoKey) => { const KeyID = Number(name2code[geoKey]); - GeoKeyDirectory.push(KeyID); + const tagType = fieldTagTypes[KeyID]; + const val = metadata[geoKey]; + if (val === undefined) { + return; + } let Count; let TIFFTagLocation; let valueOffset; - if (fieldTagTypes[KeyID] === 'SHORT') { + if (tagType === 'SHORT') { Count = 1; TIFFTagLocation = 0; - valueOffset = metadata[geoKey]; - } else if (geoKey === 'GeogCitationGeoKey') { - Count = metadata.GeoAsciiParams.length; - TIFFTagLocation = Number(name2code.GeoAsciiParams); - valueOffset = 0; + valueOffset = val; + } else if (tagType === 'ASCII') { + if (!metadata.GeoAsciiParams) { + const valStr = `${val.toString()}\u0000`; + TIFFTagLocation = Number(name2code.GeoAsciiParams); // 34737 + valueOffset = currentAsciiOffset; + Count = valStr.length; + geoAsciiParams += valStr; + currentAsciiOffset += valStr.length; + } else { + return; + } + } else if (tagType === 'DOUBLE') { + if (!metadata.GeoDoubleParams) { + const arr = toArray(val); + TIFFTagLocation = Number(name2code.GeoDoubleParams); // 34736 + valueOffset = currentDoubleIndex; + Count = arr.length; + arr.forEach((v) => { + geoDoubleParams.push(Number(v)); + currentDoubleIndex++; + }); + } else { + return; + } } else { - console.log(`[geotiff.js] couldn't get TIFFTagLocation for ${geoKey}`); + console.warn(`[geotiff.js] couldn't get TIFFTagLocation for ${geoKey}`); + return; } - GeoKeyDirectory.push(TIFFTagLocation); - GeoKeyDirectory.push(Count); - GeoKeyDirectory.push(valueOffset); + + GeoKeyDirectory.push(KeyID, TIFFTagLocation, Count, valueOffset); + validKeys++; }); + + // Write GeoKeyDirectory, GeoAsciiParams, GeoDoubleParams + GeoKeyDirectory[3] = validKeys; metadata.GeoKeyDirectory = GeoKeyDirectory; + if (!metadata.GeoAsciiParams && geoAsciiParams.length > 0) { + metadata.GeoAsciiParams = geoAsciiParams; + } + if (!metadata.GeoDoubleParams && geoDoubleParams.length > 0) { + metadata.GeoDoubleParams = geoDoubleParams; + } } - // delete GeoKeys from metadata, because stored in GeoKeyDirectory tag + // cleanup original GeoKeys metadata, because stored in GeoKeyDirectory tag for (const geoKey of geoKeys) { if (metadata.hasOwnProperty(geoKey)) { delete metadata[geoKey]; diff --git a/src/globals.js b/src/globals.js index 677d5f98..57d1bda9 100644 --- a/src/globals.js +++ b/src/globals.js @@ -166,6 +166,8 @@ export const fieldTagTypes = { 2049: 'ASCII', 2052: 'SHORT', 2054: 'SHORT', + 2057: 'DOUBLE', + 2059: 'DOUBLE', 2060: 'SHORT', 3072: 'SHORT', 3073: 'ASCII', diff --git a/test/geotiff.spec.js b/test/geotiff.spec.js index 327462d8..1e72fc29 100644 --- a/test/geotiff.spec.js +++ b/test/geotiff.spec.js @@ -1287,6 +1287,33 @@ describe('writeTests', () => { expect(normalize(fileDirectory.StripByteCounts)).to.equal(normalize(metadata.StripByteCounts)); expect(fileDirectory.GDAL_NODATA).to.equal('0\u0000'); }); + + it('should write and read back GeoAsciiParams/GeoDoubleParams keys', async () => { + const width = 2; + const height = 2; + const data = new Float32Array(width * height).fill(1); + + const metadata = { + width, + height, + GeographicTypeGeoKey: 4326, + GTModelTypeGeoKey: 2, + GeogSemiMajorAxisGeoKey: 6378137.0, // DOUBLE + GeogInvFlatteningGeoKey: 298.257223563, // DOUBLE + GeogCitationGeoKey: 'WGS 84', // ASCII + PCSCitationGeoKey: 'test-ascii', // ASCII + }; + + const buffer = await writeArrayBuffer(data, metadata); + const tiff = await fromArrayBuffer(buffer); + const image = await tiff.getImage(); + + const geoKeys = image.getGeoKeys(); + expect(geoKeys.GeogSemiMajorAxisGeoKey).to.equal(6378137.0); + expect(geoKeys.GeogInvFlatteningGeoKey).to.equal(298.257223563); + expect(geoKeys.GeogCitationGeoKey).to.equal('WGS 84'); + expect(geoKeys.PCSCitationGeoKey).to.equal('test-ascii'); + }); }); describe('BlockedSource Test', () => {