diff --git a/src/ol/proj/Units.js b/src/ol/proj/Units.js index ecf1f4874a..c8f13c3bed 100644 --- a/src/ol/proj/Units.js +++ b/src/ol/proj/Units.js @@ -8,6 +8,11 @@ * @enum {string} */ const Units = { + /** + * Radians + * @api + */ + RADIANS: 'radians', /** * Degrees * @api @@ -40,6 +45,26 @@ const Units = { USFEET: 'us-ft', }; +/** + * See http://duff.ess.washington.edu/data/raster/drg/docs/geotiff.txt + * @type {Object} + */ +const unitByCode = { + '9001': Units.METERS, + '9002': Units.FEET, + '9003': Units.USFEET, + '9101': Units.RADIANS, + '9102': Units.DEGREES, +}; + +/** + * @param {number} code Unit code. + * @return {Units} Units. + */ +export function fromCode(code) { + return unitByCode[code]; +} + /** * Meters per unit lookup table. * @const @@ -48,6 +73,7 @@ const Units = { */ export const METERS_PER_UNIT = {}; // use the radius of the Normal sphere +METERS_PER_UNIT[Units.RADIANS] = 6370997 / (2 * Math.PI); METERS_PER_UNIT[Units.DEGREES] = (2 * Math.PI * 6370997) / 360; METERS_PER_UNIT[Units.FEET] = 0.3048; METERS_PER_UNIT[Units.METERS] = 1; diff --git a/src/ol/source/GeoTIFF.js b/src/ol/source/GeoTIFF.js index 8abee1a59b..1ee25e94ff 100644 --- a/src/ol/source/GeoTIFF.js +++ b/src/ol/source/GeoTIFF.js @@ -5,10 +5,11 @@ import DataTile from './DataTile.js'; import State from './State.js'; import TileGrid from '../tilegrid/TileGrid.js'; import {Pool, fromUrl as tiffFromUrl, fromUrls as tiffFromUrls} from 'geotiff'; +import {Projection, get as getCachedProjection} from '../proj.js'; import {create as createDecoderWorker} from '../worker/geotiff-decoder.js'; import {getIntersection} from '../extent.js'; -import {get as getProjection} from '../proj.js'; import {toSize} from '../size.js'; +import {fromCode as unitsFromCode} from '../proj/Units.js'; /** * @typedef {Object} SourceInfo @@ -25,10 +26,22 @@ import {toSize} from '../size.js'; * near-infrared band, configure `bands: [4]`. */ +/** + * @typedef {Object} GeoKeys + * @property {number} GTModelTypeGeoKey Model type. + * @property {number} GTRasterTypeGeoKey Raster type. + * @property {number} GeogAngularUnitsGeoKey Angular units. + * @property {number} GeogInvFlatteningGeoKey Inverse flattening. + * @property {number} GeogSemiMajorAxisGeoKey Semi-major axis. + * @property {number} GeographicTypeGeoKey Geographic coordinate system code. + * @property {number} ProjLinearUnitsGeoKey Projected linear unit code. + * @property {number} ProjectedCSTypeGeoKey Projected coordinate system code. + */ + /** * @typedef {Object} GeoTIFFImage * @property {Object} fileDirectory The file directory. - * @property {Object} geoKeys The parsed geo-keys. + * @property {GeoKeys} geoKeys The parsed geo-keys. * @property {boolean} littleEndian Uses little endian byte order. * @property {Object} tiles The tile cache. * @property {boolean} isTiled The image is tiled. @@ -91,6 +104,49 @@ function getResolution(image, referenceImage) { } } +/** + * @param {GeoTIFFImage} image A GeoTIFF. + * @return {import("../proj/Projection.js").default} The image projection. + */ +function getProjection(image) { + const geoKeys = image.geoKeys; + if (!geoKeys) { + return null; + } + + if (geoKeys.ProjectedCSTypeGeoKey) { + const code = 'EPSG:' + geoKeys.ProjectedCSTypeGeoKey; + let projection = getCachedProjection(code); + if (!projection) { + const units = unitsFromCode(geoKeys.ProjLinearUnitsGeoKey); + if (units) { + projection = new Projection({ + code: code, + units: units, + }); + } + } + return projection; + } + + if (geoKeys.GeographicTypeGeoKey) { + const code = 'EPSG:' + geoKeys.GeographicTypeGeoKey; + let projection = getCachedProjection(code); + if (!projection) { + const units = unitsFromCode(geoKeys.GeogAngularUnitsGeoKey); + if (units) { + projection = new Projection({ + code: code, + units: units, + }); + } + } + return projection; + } + + return null; +} + /** * @param {import("geotiff/src/geotiff.js").GeoTIFF|import("geotiff/src/geotiff.js").MultiGeoTIFF} tiff A GeoTIFF. * @return {Promise>} Resolves to a list of images. @@ -419,14 +475,10 @@ class GeoTIFFSource extends DataTile { const firstSource = sources[0]; for (let i = firstSource.length - 1; i >= 0; --i) { const image = firstSource[i]; - if (image.geoKeys) { - const code = - image.geoKeys.ProjectedCSTypeGeoKey || - image.geoKeys.GeographicTypeGeoKey; - if (code) { - this.projection = getProjection('EPSG:' + code); - break; - } + const projection = getProjection(image); + if (projection) { + this.projection = projection; + break; } } } diff --git a/test/browser/spec/ol/source/geotiff.test.js b/test/browser/spec/ol/source/geotiff.test.js index de76b67e98..80842afeae 100644 --- a/test/browser/spec/ol/source/geotiff.test.js +++ b/test/browser/spec/ol/source/geotiff.test.js @@ -2,7 +2,7 @@ import GeoTIFFSource from '../../../../../src/ol/source/GeoTIFF.js'; import State from '../../../../../src/ol/source/State.js'; import TileState from '../../../../../src/ol/TileState.js'; -describe('ol.source.GeoTIFF', function () { +describe('ol/source/GeoTIFF', function () { /** @type {GeoTIFFSource} */ let source; beforeEach(function () { @@ -30,6 +30,7 @@ describe('ol.source.GeoTIFF', function () { expect(source.nodataValues_).to.eql([[0]]); expect(source.getTileGrid().getResolutions().length).to.be(1); expect(source.projection.getCode()).to.be('EPSG:4326'); + expect(source.projection.getUnits()).to.be('degrees'); done(); }); });