I am working on a project which requires me work with a orthomosaic map. This map is in .tiff format. I want to extract the geographical information from that map so that I can overlay it correctly on a google map. I am using mean stack for this project.
So my final question is: Is there a way to extract geographical tags from a tiff image using javascript?
EDIT : When I use the Maptiler(https://www.maptiler.com/) it automatically places the .tif file correctly on the google map. I want to know how it extracts the information and places it on the map so that I can do it myself.
In order to read the TIFF tags, you'll need a proper TIFF parser, like the tiff
package, so that you can run through the non-bitmap parts of an IFD block:
import fs from "node:fs";
import tiff from "tiff";
// Load a GeoTIFF file
const file = fs.readFileSync(`ALPSMLC30_S045E168_DSM.tif`);
// We can do this in the browser, too, the `decode` function takes an ArrayBuffer
const image = tiff.decode(file.buffer);
// get the first IFD block's pixels and fields
const { data:pixels, fields, width, height } = image[0];
// let's see what tags apply
console.log(fields);
Running this on the example file from the ALOS Word 3D - 30m dataset yields:
Map(19) {
254 => 0,
256 => 3600,
257 => 3600,
258 => 16,
259 => 1,
262 => 1,
273 => Uint32Array(3600) [
... 3600 more items
],
274 => 1,
277 => 1,
278 => 1,
279 => Uint32Array(3600) [
... 3600 more items
],
282 => 1,
283 => 1,
284 => 1,
339 => 2,
33550 => Float64Array(3) [
0.0002777777777777778,
0.0002777777777777778,
0
],
33922 => Float64Array(6) [
0,
0,
0,
168,
-44,
0
],
34735 => Uint16Array(24) [
1, 1, 0, 5,
1024, 0, 1, 2,
1025, 0, 1, 1,
2048, 0, 1, 4326,
2052, 0, 1, 9001,
2054, 0, 1, 9102
],
34737 => 'WGS-84'
}
I want to know how it extracts the information and places it on the map so that I can do it myself.
Strap in, this is going to get detailed.
The tag numbers are all well-defined things, and can be found listed over on (the now unfortunately intentionally no longer available) https://www.awaresystems.be/imaging/tiff/tifftags.html, where all the low numbered ones are universal TIFF tags, e.g.
The higher number ones are "private" tags, only meaningful when the TIFF is used in a specific context. For example, that 339
tells us pixel values are signed integers that have been encoded using two's complement, 33550
is the model pixel scale tag, 34737
is the GeoASCII params tag, etc.
In this TIFF's tag set 34735
is arguably the most important tag, as it houses the GeoKey dictionary that is used by mapping software to properly place images on the world map:
The first line, 1 1 0 5
, reads:
Each entry in the GeoKey dictionary is ordered as "key, tifftaglocation, count, value(s)", with the tifftaglocation
field having either value 0 (all "value" fields use SHORT, an unsigned 16 bit integer datatype) or 1 (the tiff tag's formal specification tells you how to decode the value), and the count
field tells us how many records we need to parse, immediately following the count value.
In this case, there are five:
For doing map-related things (in absence of tools that can automatically place your tiff for you), we look at tag 33500 (which gives us our map scale) and 33922 (which gives us our map translation):
...
33550 => Float64Array(3) [
0.0002777777777777778,
0.0002777777777777778,
0
],
33922 => Float64Array(6) [
0, 0, 0,
168, -44, 0
],
...
The values in 33922 tell us that pixel index x=0, y=0 (with an unused z-value 0) maps to real world arc degree coordinate x=168, y=-44 (also with an unused z-value 0) i.e. S44 E168 just off the West coast of New Zealand's South Island, and 33550 tells us that pixels in the image are spaced 1 degree of arc * 0.00027[...] = 1/3600 arc degree = 1 arc second = 30.87 meters
apart. The same value is used for both the x and y components, so both x and y spacing is 30.87 meters, which lets us determine the proper translation and scaling for placing and aligning this data on a map.
It also lets us compute the "GPS coordinate given a TIFF pixel" and "TIFF pixel given a GPS coordinate". Based on https://gdal.org/tutorials/geotransforms_tut.html:
function transform(matrix, x, y) {
return [
matrix[0] + matrix[1] * x + matrix[2] * y,
matrix[3] + matrix[4] * x + matrix[5] * y,
];
}
let [sx, sy, _sz] = fields.get(33550);
const [
_px, _py, _k,
gx, gy, _gz
] = fields.get(33922);
// Just like SVG or <canvas>, GeoTIFF uses a "flipped" y coordinate.
sy = -sy;
// Our "forward transform" goes from pixels to geographic coordinate,
// and is the (partial) matrix from the link above.
const pixelToGeoMatrix = [
gx, sx, 0,
gy, 0, sy
];
// Our "reverse transform" is literally the inverse matrix operation,
// converting geographic coordinates to pixels.
const geoToPixelMatrix = [
-gx/sx, 1/sx, 0,
-gy/sy, 0, 1/sy
];
function pixelToGeo(x, y) {
const [lat, long] = transform(pixelToGeoMatrix, x, y);
return { lat, long };
}
function geoToPixel(long, lat) {
const [x, y] = transform(geoToPixelMatrix, long, lat);
return { x: x|0, y: y|0 };
}
(the transform reversal is explained over on https://gis.stackexchange.com/a/452575/219296)
Using this code, we can convert from GPS coordinate to pixel coordinate and back. For example, running the following code:
const lat = 168.321971;
const long = -44.9856891;
const { x, y } = geoToPixel(long, lat);
const elevation = pixels[x + y * width];
console.log(`GPS: (${lat},${long}), PX: (${x},${y}), elevation: ${elevation}m`);
yields the following log for the above TIFF data:
GPS: (-44.9856891,168.321971), PX: (1159,3548), elevation: 1421m
Or going the other way:
const [ x, y ] = [
(Math.random() * width)|0,
(Math.random() * height)|0
];
const { lat, long } = pixelToGeo(x, y);
console.log(`PX: (${x},${y}) maps to GPS: (${lat},${long})`);
yields the following log for the above TIFF data:
PX: (278,1882) maps to GPS: (-44.522777777777776,168.07722222222222)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With