Is there a way to extract geographical tags from a tiff image using javascript?

1.4k Views Asked by At

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.

3

There are 3 best solutions below

0
On

There is open source library called open layers https://openlayers.org/. This is the best way to overlay your tiff image and once to include the tiff image on the this layer you can extract any information you need.

You can use Angular with typescript or java script, that is preferable. Basically you can get each and every details in a manner of coordinates from your tiff image using this library.

Setting up this library is an complecated project I just gave you an idea how you can accopmlish. I would like to help you in this matter. I know this is not much I explained and I cant give yo much details since I do not know detail about this problem. If you need any help contact me.

5
On

If you are working with GeoTIFF, see @Mike's answer: https://stackoverflow.com/a/75647596/1192115 it describes in detail how to get geotags

If you have a PhotoCam that has images in a TIFF format and you need to extract where it was made, you can check EXIF. To do it you can use [exif npm library][3]

const ExifImage = require('exif').ExifImage;
 
try {
    new ExifImage({ image : 'your.tiff' }, function (error, exifData) {
        if (error)
            console.log('Error: '+error.message);
        else {
            let location = exifData.gps || 'who knows, maybe somwhere else :(';
            console.log(location); // pass location to google maps after some transforming 
        } 
    });
} catch (error) {
    console.log('Error: ' + error.message);
}
4
On

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 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);

yields (for this specific GeoTIFF):

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 https://www.awaresystems.be/imaging/tiff/tifftags.html, where all the low numbered ones are universal TIFF tags (256 and 257 are the image width and height, 274 is "is this image rotated", 284 tells us whether pixels are stored RGBRGBetc or as separate R, G, and B arrays, etc.) and the higher number ones are "private" tags, only used in specific context. So for example that 339 tells us pixels store signed integers using two's complement, 33550 is the model pixel scale tag, 34737 is the GeoASCII params tag, etc.

In this, 34735 is arguably the most important tag, as it houses the GeoKey dictionary that is used by mapping software to properly place images:

The first line, 1 1 0 5, reads:

  • "Tiff version 1",
  • "keys revision 1.0" (encoded as separate "before the period" and "after the period" integer values),
  • "there are 5 entries in this dictionary"

Each entry in the GeoKey dictionary is ordered as "key, tifftaglocation, count, value(s)", with the tifftaglocation field being either 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), with the count field telling us how many SHORTs the value consists of.

In this case:

  • 1024 (model type) has value 2: the data represents a mapping based on the Geographic latitude-longitude System.
  • 1025 (raster type) has value 1: Each pixel represents an area (e.g it's an averaged height map, as opposed to a pixel being a specific point with an exact known elevation, but at that point only)
  • 2048 (geographic type) has value 4326: This is WGS84 data, e.g. it uses (Web) Mercator projection, used by pretty much all mapling software
  • 2052 (linear units) has value 9001: The data uses EPSG Linear Units (i.e. "decimal degrees" so a value of 38.25 means 38 whole units plus a quarter of a unit)
  • 2054 (angular units) has value 9102: The angular aspect of the data is "degrees of arc", so combined with the previous value we know that if we see 38.25 that means 38.25 * 1 arc degree.

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, i.e. S44 E168 (also with an unused z-value 0) 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, in both the x and y direction, 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 pixel_to_geo = [
  gx,  sx,   0,
  gy,   0,  sy
];

// Our "reverse transform" is literally the inverse matrix operation,
// converting geographic coordinates to pixels. 
const geo_to_pixel = [
  -gx/sx,  1/sx,   0,
  -gy/sy,   0,    1/sy
];

function pixelToGeo(x, y) {
  const [lat, long] = transform(pixel_to_geo, x, y);
  return { lat, long };
}

function geoToPixel(long, lat) {
  const [x, y] = transform(geo_to_pixel, 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)