"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = geotiff_read_bbox; exports.snap_to_read_window = snap_to_read_window; var _geotiffEpsgCode = _interopRequireDefault(require("geotiff-epsg-code")); var _geotiffGeotransform = _interopRequireDefault(require("geotiff-geotransform")); var _reproject = _interopRequireDefault(require("bbox-fns/reproject.js")); var _scale = _interopRequireDefault(require("bbox-fns/scale.js")); var _intersect = _interopRequireDefault(require("bbox-fns/intersect.js")); var _bboxSize = _interopRequireDefault(require("bbox-fns/bbox-size.js")); var _proj4Collect = _interopRequireDefault(require("proj4-collect")); var _Geotransform = _interopRequireDefault(require("geoaffine/Geotransform.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // convert ij bbox to read window used by geotiff.js function snap_to_read_window([xmin, ymin, xmax, ymax], padding) { const [padX, padY] = padding || [0, 0]; return [Math.floor(xmin) - padX, Math.floor(ymin) - padY, Math.ceil(xmax) + padX, Math.ceil(ymax) + padY]; } async function geotiff_read_bbox({ bbox, clamp = false, debug_level = 0, density = 100, fill_value, geotiff, geotiff_srs, padding, proj4: custom_proj4, srs: srs_of_bbox, use_overview = false, target_height, target_width }) { if (debug_level >= 1) console.log("[geotiff-read-bbox] starting"); if (geotiff === null) throw new Error("[geotiff-read-bbox] geotiff is null"); if (geotiff === undefined) throw new Error("[geotiff-read-bbox] geotiff is undefined"); const start_get_image = performance.now(); const image = await geotiff.getImage(); const duration_get_image = performance.now() - start_get_image; if (debug_level >= 2) { console.log(`[geotiff-read-bbox] getting first image took ${Math.round(duration_get_image)} ms`); } const image_height = image.getHeight(); const image_width = image.getWidth(); if (debug_level >= 2) console.log("[geotiff-read-bbox] image_height:", image_height); if (debug_level >= 2) console.log("[geotiff-read-bbox] image_width:", image_width); if (!geotiff_srs) geotiff_srs = await (0, _geotiffEpsgCode.default)(geotiff); if (debug_level >= 2) console.log("[geotiff-read-bbox] geotiff_srs:", geotiff_srs); if ([undefined, null, 32767].includes(geotiff_srs) && srs_of_bbox !== "simple") { throw new Error("[geotiff-read-bbox] unable to parse SRS of geotiff"); } // normalize srs information if (typeof geotiff_srs === "number") geotiff_srs = "EPSG:" + geotiff_srs; if (typeof srs_of_bbox === "number") srs_of_bbox = "EPSG:" + srs_of_bbox; const fd = image.fileDirectory; if (!(fd.ModelTransformation || fd.ModelTiepoint && fd.ModelPixelScale)) { throw new Error("GeoTIFF doesn't have ModelTransformation nor ModelTiepoint+ModelPixelScale"); } const geotransform = (0, _geotiffGeotransform.default)(image); if (debug_level >= 1) console.log("[geotiff-read-bbox] geotransform:", geotransform); const affine = (0, _Geotransform.default)(geotransform); if (debug_level >= 1) console.log("[geotiff-read-bbox] affine:", affine); const proj4 = (0, _proj4Collect.default)([custom_proj4]); if (debug_level >= 1) console.log("[geotiff-read-bbox] proj4:", typeof proj4); let bbox_in_base_image_coords; if (srs_of_bbox === "simple") { const [xmin, ymin, xmax, ymax] = bbox; bbox_in_base_image_coords = [xmin, image.getHeight() - ymax, // how many pixels from top of geotiff, xmax, image.getHeight() - ymin // how many pixels from top of geotiff ]; } else { let convert_from_srs_of_bbox_to_px_of_geotiff; let convert_from_px_of_geotiff_to_srs_of_bbox; let convert_from_srs_of_geotiff_to_srs_of_bbox; let convert_from_srs_of_bbox_to_srs_of_geotiff; if (geotiff_srs === srs_of_bbox) { if (debug_level >= 2) console.log("[geotiff-read-bbox] srs of geotiff and bbox are the same!"); convert_from_srs_of_geotiff_to_srs_of_bbox = pt => pt; convert_from_srs_of_bbox_to_srs_of_geotiff = pt => pt; convert_from_srs_of_bbox_to_px_of_geotiff = xy => affine.inverse(xy); convert_from_px_of_geotiff_to_srs_of_bbox = ij => affine.forward(ij); } else { // check if srs is in proj4 defs if not wkt nor proj4 string if (!geotiff_srs.includes("[") && !geotiff_srs.includes("+") && !proj4.defs[geotiff_srs]) throw new Error("[geotiff-read-bbox] unrecognized srs: " + geotiff_srs); if (!srs_of_bbox.includes("[") && !srs_of_bbox.includes("+") && !proj4.defs[srs_of_bbox]) throw new Error("[geotiff-read-bbox] unrecognized srs: " + srs_of_bbox); ({ forward: convert_from_srs_of_geotiff_to_srs_of_bbox, inverse: convert_from_srs_of_bbox_to_srs_of_geotiff } = proj4(geotiff_srs, srs_of_bbox)); convert_from_srs_of_bbox_to_px_of_geotiff = xy => affine.inverse(convert_from_srs_of_bbox_to_srs_of_geotiff(xy)); convert_from_px_of_geotiff_to_srs_of_bbox = ij => convert_from_srs_of_geotiff_to_srs_of_bbox(affine.forward(ij)); } // convert bounding box in arbitrary spatial reference system to image coordinates in geotiff bbox_in_base_image_coords = (0, _reproject.default)(bbox, convert_from_srs_of_bbox_to_px_of_geotiff, { density, nan_strategy: "skip" }); } if (debug_level >= 2) console.log("[geotiff-read-bbox] bbox_in_base_image_coords:", bbox_in_base_image_coords); // read window as used by geotiff.js let read_window = snap_to_read_window(bbox_in_base_image_coords, padding); if (debug_level >= 2) console.log("[geotiff-read-bbox] read_window:", read_window); let [read_width, read_height] = (0, _bboxSize.default)(read_window); if (debug_level >= 2) console.log("[geotiff-read-bbox] base read size:", [read_width, read_height]); const selected = { image, image_height, image_width, index: 0, ratio: [1, 1], read_window, read_height, read_width }; if (use_overview && target_height && target_width) { // figure out ratio between actual raw width and desired width if (debug_level >= 2) console.log("[geotiff-read-bbox] getting image count"); const start_get_image_count = performance.now(); const image_count = await geotiff.getImageCount(); if (debug_level >= 2) console.log("[geotiff-read-bbox] image_count: " + image_count); const duration_get_image_count = performance.now() - start_get_image_count; if (debug_level >= 2) { console.log(`[geotiff-read-bbox] getting image count (${image_count}) took ${duration_get_image_count} ms`); } for (let i = 1; i < image_count; i++) { const subimage = await geotiff.getImage(i); if (debug_level >= 3) console.log("[geotiff-read-bbox] subimage:", typeof subimage); if (subimage.fileDirectory.PhotometricInterpretation === 4) { if (debug_level >= 3) console.log(`[geotiff-read-bbox] ignoring image ${i} because it is a transparency mask`); continue; } // from 0 to 1 const ratioX = subimage.getHeight() / image_height; if (debug_level >= 3) console.log("[geotiff-read-bbox] ratioX:", ratioX); const ratioY = subimage.getWidth() / image_width; if (debug_level >= 3) console.log("[geotiff-read-bbox] ratioY:", ratioY); const bbox_in_subimage_coords = (0, _scale.default)(bbox_in_base_image_coords, [ratioX, ratioY]); if (debug_level >= 3) console.log("[geotiff-read-bbox] bbox_in_subimage_coords:", bbox_in_subimage_coords); const subimage_read_window = snap_to_read_window(bbox_in_subimage_coords, padding); if (debug_level >= 2) console.log("[geotiff-read-bbox] subimage_read_window:", subimage_read_window); // how many pixels we would clip from the current image [read_width, read_height] = (0, _bboxSize.default)(subimage_read_window); if (debug_level >= 3) console.log("[geotiff-read-bbox] read_width:", read_width); if (debug_level >= 3) console.log("[geotiff-read-bbox] read_height:", read_height); if (read_height >= target_height && read_width >= target_width) { selected.image = subimage; selected.image_height = subimage.getHeight(); selected.image_width = subimage.getWidth(); selected.index = i; selected.ratio = [ratioX, ratioY]; selected.read_height = read_height; selected.read_width = read_width; selected.read_window = subimage_read_window; if (debug_level >= 2) console.log("[geotiff-read-bbox] selected:", selected); } else { break; } } } const start_read_rasters = performance.now(); let data; if (debug_level >= 3) console.log("[geotiff-read-bbox] selected:", selected); const [left, top, right, bottom] = selected.read_window; if (right <= 0 || bottom <= 0 || left >= selected.image_width || top >= selected.image_height) { // totally outside raster, so skip geotiff.js const depth = image.getSamplesPerPixel(); const read_area = selected.read_width * selected.read_height; data = []; for (let b = 0; b < depth; b++) data.push(new Array(read_area).fill(fill_value)); } else { if (clamp) { if (debug_level >= 2) console.log("[geotiff-read-bbox] clamping"); selected.read_window = (0, _intersect.default)(selected.read_window, [0, 0, selected.image_width, selected.image_height]); [read_width, read_height] = (0, _bboxSize.default)(selected.read_window); selected.read_height = read_height; selected.read_width = read_width; } if (debug_level >= 2) console.log("[geotiff-read-bbox] reading ", selected.read_window); data = await selected.image.readRasters({ fillValue: fill_value, window: selected.read_window }); } const duration_read_rasters = performance.now() - start_read_rasters; if (debug_level >= 2) { console.log("[geotiff-read-bbox] reading rasters took " + duration_read_rasters.toFixed() + "ms"); } if (debug_level >= 3) console.log("[geotiff-read-bbox] data:", data); const unscaled_read_window = (0, _scale.default)(selected.read_window, selected.ratio.map(n => 1 / n)); const read_bbox = (0, _reproject.default)(unscaled_read_window, affine.forward, { density }); // create geotransform equation for current data const [scaled_left, scaled_top, scaled_right, scaled_bottom] = unscaled_read_window; const upper_left = [scaled_left, scaled_top]; if (debug_level >= 3) console.log("[geotiff-read-bbox] upper_left:", upper_left); const [upper_left_x, upper_left_y] = affine.forward(upper_left); const read_geotransform = [upper_left_x, geotransform[1] / selected.ratio[0], geotransform[2] / selected.ratio[1], upper_left_y, geotransform[4] / selected.ratio[0], geotransform[5] / selected.ratio[1]]; const simple_bbox = [unscaled_read_window[0], image_height - unscaled_read_window[3], unscaled_read_window[2], image_height - unscaled_read_window[1]]; const result = { base_window: unscaled_read_window, bbox: read_bbox, data, geotransform: read_geotransform, height: selected.read_height, index: selected.index, image: selected.image, simple_bbox, srs: geotiff_srs, width: selected.read_width, window: selected.read_window }; [["read_bbox", "bbox"], ["selected_image_index", "index"], ["selected_image", "image"]].forEach(([_from, _to]) => { Object.defineProperty(result, _from, { get() { if (debug_level >= 1) console.log(`[geotiff-read-bbox] ${_from} is deprected, please use ${_to} instead`); return result[_to]; } }); }); Object.defineProperty(result, "read_window", { get() { if (debug_level >= 1) { console.log("[geotiff-read-bbox] read_window [left, bottom, top, right] is deprected, please use window [left, top, right, bottom] instead"); } return [this.window[0], this.window[3], this.window[2], this.window[1]]; } }); Object.defineProperty(result, "srs_of_geotiff", { get() { if (debug_level >= 1) console.log("[geotiff-read-bbox] srs_of_geotiff is deprected, please use srs instead"); return this.srs.startsWith("EPSG:") ? Number(this.srs.replace("EPSG:", "")) : this.srs; } }); return result; } module.exports = exports.default; module.exports.default = exports.default;