Compare commits

...

4 Commits
3.7 ... avm

Author SHA1 Message Date
Matthieu Baumann
3d060c12e9 remove tiff dep 2024-07-29 09:29:30 +02:00
bmatthieu3
dd55f8904b wip tiff support 2024-07-26 19:07:41 +02:00
Matthieu Baumann
2c0afd8b84 wip parse the header inside xmp 2024-07-26 09:41:55 +02:00
bmatthieu3
245030fb0a wip: image format inference, avm parser based on https://www.strudel.org.uk/avm/js/, wcs creation from avm tags 2024-07-24 17:52:57 +02:00
11 changed files with 583 additions and 115 deletions

View File

@@ -14,7 +14,7 @@
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: "05 40 59.12 -02 27 04.1", fov: 2});
let survey = aladin.createImageSurvey('DSS2 local', "local hips", "hips/CDS_P_DSS2_color");
let survey = aladin.createImageSurvey('DSS2 local', "local hips", "./hips/CDS_P_DSS2_color");
aladin.setBaseImageLayer(survey);
});
</script>

View File

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 30, survey: "CDS/P/DSS2/color", target: "280 +0", projection: "AIT", showShareControl:true, showSettingsControl: true, showContextMenu:true});
aladin.setOverlayImageLayer(A.image(
"https://www.virtualastronomy.org/images/sig05-013.jpg",
{
name: "sig05-017",
successCallback: (ra, dec, fov, image) => {
console.log(ra, dec)
aladin.gotoRaDec(ra, dec);
aladin.setFoV(fov * 5)
}
},
));
});
</script>
</body>
</html>

View File

@@ -13,7 +13,6 @@
"https://nova.astrometry.net/image/25038473?filename=M61.jpg",
{
name: "M61",
imgFormat: 'jpeg',
wcs: {
NAXIS: 0, // Minimal header
CTYPE1: 'RA---TAN', // TAN (gnomic) projection
@@ -28,9 +27,9 @@
CUNIT1: 'deg', // X pixel scale units
CUNIT2: 'deg', // Y pixel scale units
CD1_1: -0.000223666022989, // Transformation matrix
CD1_2: 0.000296578064584, // no comment
CD1_2: -0.000296578064584, // no comment
CD2_1: -0.000296427555509, // no comment
CD2_2: -0.000223774308964, // no comment
CD2_2: 0.000223774308964, // no comment
NAXIS1: 1080, // Image width, in pixels.
NAXIS2: 705 // Image height, in pixels.
},

View File

@@ -90,7 +90,6 @@ pub struct App {
_fbo_view: FrameBufferObject,
_fbo_ui: FrameBufferObject,
//line_renderer: RasterizedLineRenderer,
colormaps: Colormaps,
pub projection: ProjectionType,
@@ -1115,7 +1114,6 @@ impl App {
pub(crate) fn add_image_fits(
&mut self,
id: String,
stream: web_sys::ReadableStream,
meta: ImageMetadata,
layer: String,
@@ -1245,8 +1243,9 @@ impl App {
} else {
let fits = ImageLayer {
images,
id: layer.clone(),
layer,
id,
meta,
};
@@ -1301,6 +1300,7 @@ impl App {
) -> Result<(), JsValue> {
let old_meta = self.layers.get_layer_cfg(&layer)?;
// Set the new meta
// keep the old meta data
let new_img_fmt = meta.img_format;
self.layers
.set_layer_cfg(layer.clone(), meta, &mut self.camera, &self.projection)?;

View File

@@ -55,8 +55,10 @@ pub fn build_fov_coverage(
moc
}
} else {
let center_xyzw = crate::coosys::apply_coo_system(camera_frame, frame, camera_center);
let biggest_fov_rad = proj.aperture_start().to_radians();
let lonlat = camera_center.lonlat();
let lonlat = center_xyzw.lonlat();
HEALPixCoverage::from_cone(&lonlat, biggest_fov_rad * 0.5, depth)
}
}

View File

@@ -382,21 +382,19 @@ impl WebClient {
#[wasm_bindgen(js_name = addImageFITS)]
pub fn add_image_fits(
&mut self,
id: String,
stream: web_sys::ReadableStream,
cfg: JsValue,
layer: String,
) -> Result<js_sys::Promise, JsValue> {
let cfg: ImageMetadata = serde_wasm_bindgen::from_value(cfg)?;
//let wcs: Option<WCSParams> = serde_wasm_bindgen::from_value(wcs)?;
self.app.add_image_fits(id, stream, cfg, layer)
self.app.add_image_fits(stream, cfg, layer)
}
#[wasm_bindgen(js_name = addImageWithWCS)]
pub fn add_image_with_wcs(
&mut self,
data: web_sys::ReadableStream,
stream: web_sys::ReadableStream,
wcs: JsValue,
cfg: JsValue,
layer: String,
@@ -406,7 +404,7 @@ impl WebClient {
let wcs_params: WCSParams = serde_wasm_bindgen::from_value(wcs)?;
let wcs = WCS::new(&wcs_params).map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
self.app.add_image_from_blob_and_wcs(layer, data, wcs, cfg)
self.app.add_image_from_blob_and_wcs(layer, stream, wcs, cfg)
}
#[wasm_bindgen(js_name = removeLayer)]

View File

@@ -11,6 +11,6 @@ uniform float opacity;
#include ../hips/color.glsl;
void main() {
out_frag_color = texture(tex, frag_uv);
out_frag_color = texture(tex, vec2(frag_uv.x, 1.0 - frag_uv.y));
out_frag_color.a = out_frag_color.a * opacity;
}

View File

@@ -6,7 +6,7 @@ vec2 w2c_ait(vec3 p) {
float y2d = p.y / w;
float x2d = 0.0;
if (abs(p.x) < 1e-3) {
if (abs(p.x) < 5e-3) {
float x_over_r = p.x/r;
x2d = -p.x * (1.0 - x_over_r*x_over_r/21.0) / w;
} else {

View File

@@ -28,6 +28,8 @@
import { ALEvent } from "./events/ALEvent.js";
import { ColorCfg } from "./ColorCfg.js";
import { Aladin } from "./Aladin.js";
import { Utils } from "./Utils";
import { AVM } from "./libs/avm.js";
/**
* @typedef {Object} ImageOptions
@@ -48,7 +50,7 @@ import { Aladin } from "./Aladin.js";
* @property {number} [contrast=0.0] - The contrast value for the color configuration.
* @property {Object} [wcs] - an object describing the WCS of the image. In case of a fits image
* this property will be ignored as the WCS taken will be the one present in the fits file.
* @property {number} [imgFormat='fits'] - The image format of the image to load.
* @property {string} [imgFormat] - Optional image format. Giving it will prevent the auto extension determination algorithm to be triggered. Possible values are 'jpeg', 'png' or 'fits'. tiff files are not supported. You can convert your tiff files to jpg ones by using the fantastic image magick suite.
*
* @example
*
@@ -56,26 +58,25 @@ import { Aladin } from "./Aladin.js";
* "https://nova.astrometry.net/image/25038473?filename=M61.jpg",
* {
* name: "M61",
* imgFormat: 'jpeg',
* wcs: {
* NAXIS: 0, // Minimal header
* CTYPE1: 'RA---TAN', // TAN (gnomic) projection + SIP distortions
* CTYPE2: 'DEC--TAN', // TAN (gnomic) projection + SIP distortions
* EQUINOX: 2000.0, // Equatorial coordinates definition (yr)
* LONPOLE: 180.0, // no comment
* LATPOLE: 0.0, // no comment
* CRVAL1: 185.445488837, // RA of reference point
* CRVAL2: 4.47896032431, // DEC of reference point
* CRPIX1: 588.995094299, // X reference pixel
* CRPIX2: 308.307905197, // Y reference pixel
* CUNIT1: 'deg', // X pixel scale units
* CUNIT2: 'deg', // Y pixel scale units
* CD1_1: -0.000223666022989, // Transformation matrix
* CD1_2: 0.000296578064584, // no comment
* CD2_1: -0.000296427555509, // no comment
* CD2_2: -0.000223774308964, // no comment
* NAXIS1: 1080, // Image width, in pixels.
* NAXIS2: 705 // Image height, in pixels.
NAXIS: 0, // Minimal header
CTYPE1: 'RA---TAN', // TAN (gnomic) projection
CTYPE2: 'DEC--TAN', // TAN (gnomic) projection
EQUINOX: 2000.0, // Equatorial coordinates definition (yr)
LONPOLE: 180.0, // no comment
LATPOLE: 0.0, // no comment
CRVAL1: 185.445488837, // RA of reference point
CRVAL2: 4.47896032431, // DEC of reference point
CRPIX1: 588.995094299, // X reference pixel
CRPIX2: 308.307905197, // Y reference pixel
CUNIT1: 'deg', // X pixel scale units
CUNIT2: 'deg', // Y pixel scale units
CD1_1: -0.000223666022989, // Transformation matrix
CD1_2: -0.000296578064584, // no comment
CD2_1: -0.000296427555509, // no comment
CD2_2: 0.000223774308964, // no comment
NAXIS1: 1080, // Image width, in pixels.
NAXIS2: 705 // Image height, in pixels.
* },
* successCallback: (ra, dec, fov, image) => {
* aladin.gotoRaDec(ra, dec);
@@ -104,8 +105,8 @@ export let Image = (function () {
this.url = url;
this.id = url;
this.name = (options && options.name) || this.url;
this.imgFormat = (options && options.imgFormat) || "fits";
this.formats = [this.imgFormat];
this.imgFormat = options && options.imgFormat;
//this.formats = [this.imgFormat];
// callbacks
this.successCallback = options && options.successCallback;
this.errorCallback = options && options.errorCallback;
@@ -116,7 +117,7 @@ export let Image = (function () {
}*/
this.colorCfg = new ColorCfg(options);
this.options = options;
this.options = options || {};
let self = this;
@@ -219,92 +220,185 @@ export let Image = (function () {
}
};
Image.prototype._addFITS = function(layer) {
let self = this;
return Utils.fetch({
url: this.url,
dataType: 'readableStream',
success: (stream) => {
return self.view.wasm.addImageFITS(
stream,
{
...self.colorCfg.get(),
longitudeReversed: false,
imgFormat: 'fits',
},
layer
)
},
error: (e) => {
// try as cors
const url = Aladin.JSONP_PROXY + '?url=' + self.url;
return Utils.fetch({
url: url,
dataType: 'readableStream',
success: (stream) => {
return self.view.wasm.addImageFITS(
stream,
{
...self.colorCfg.get(),
longitudeReversed: false,
imgFormat: 'fits',
},
layer
)
},
});
}
})
.then((imageParams) => {
self.imgFormat = 'fits';
return Promise.resolve(imageParams);
})
}
Image.prototype._addJPGOrPNG = function(layer) {
let self = this;
let img = document.createElement('img');
return new Promise((resolve, reject) => {
img.src = this.url;
img.crossOrigin = "Anonymous";
img.onload = () => {
const img2Blob = () => {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
const blob = new Blob([imageData.data]);
const stream = blob.stream(1024);
resolve(stream)
};
if (!self.options.wcs) {
/* look for avm tags if no wcs is given */
let avm = new AVM(img);
avm.load((obj) => {
// obj contains the following information:
// obj.id (string) = The ID provided for the image
// obj.img (object) = The image object
// obj.xmp (string) = The raw XMP header
// obj.wcsdata (Boolean) = If WCS have been loaded
// obj.tags (object) = An array containing all the loaded tags e.g. obj.tags['Headline']
// obj.wcs (object) = The wcs parsed from the image
if (obj.wcsdata) {
if (img.width !== obj.wcs.NAXIS1) {
obj.wcs.NAXIS1 = img.width;
}
if (img.height !== obj.wcs.NAXIS2) {
obj.wcs.NAXIS2 = img.height;
}
self.options.wcs = obj.wcs;
img2Blob()
} else {
// no tags found
reject('No WCS have been found in the image')
return;
}
})
} else {
img2Blob()
}
}
let proxyUsed = false;
img.onerror = (e) => {
// use proxy
if (proxyUsed) {
console.error(e);
reject('Error parsing image located at: ' + self.url)
return;
}
proxyUsed = true;
img.src = Aladin.JSONP_PROXY + '?url=' + self.url;
}
})
.then((readableStream) => {
let wcs = self.options && self.options.wcs;
wcs.NAXIS1 = wcs.NAXIS1 || img.width;
wcs.NAXIS2 = wcs.NAXIS2 || img.height;
return self.view.wasm
.addImageWithWCS(
readableStream,
wcs,
{
...self.colorCfg.get(),
longitudeReversed: false,
imgFormat: 'jpeg',
},
layer
)
})
.then((imageParams) => {
self.imgFormat = 'jpeg';
return Promise.resolve(imageParams);
})
.finally(() => {
img.remove();
});
}
Image.prototype.add = function (layer) {
this.layer = layer;
let self = this;
let promise;
if (this.imgFormat === 'fits') {
let id = this.url;
promise = fetch(this.url)
.then((resp) => resp.body)
.then((readableStream) => {
return self.view.wasm
.addImageFITS(
id,
readableStream,
{
...self.colorCfg.get(),
longitudeReversed: false,
imgFormat: self.imgFormat,
},
layer
)
promise = this._addFITS(layer)
.catch(e => {
console.error(`Image located at ${this.url} could not be parsed as fits file. Is the imgFormat specified correct?`)
return Promise.reject(e)
})
} else if (this.imgFormat === 'jpg' || this.imgFormat === 'jpeg') {
let img = document.createElement('img');
promise =
new Promise((resolve, reject) => {
img.src = this.url;
img.crossOrigin = "Anonymous";
img.onload = () => {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
const blob = new Blob([imageData.data]);
const stream = blob.stream(1024);
resolve(stream)
}
let proxyUsed = false;
img.onerror = () => {
// use proxy
if (proxyUsed) {
reject('Error parsing img ' + self.url)
return;
}
proxyUsed = true;
img.src = Aladin.JSONP_PROXY + '?url=' + self.url;
}
})
.then((readableStream) => {
let wcs = self.options && self.options.wcs;
wcs.NAXIS1 = wcs.NAXIS1 || img.width;
wcs.NAXIS2 = wcs.NAXIS2 || img.height;
return self.view.wasm
.addImageWithWCS(
readableStream,
wcs,
{
...self.colorCfg.get(),
longitudeReversed: false,
imgFormat: self.imgFormat,
},
layer
)
})
.finally(() => {
img.remove();
} else if (this.imgFormat === 'jpeg' || this.imgFormat === 'png') {
promise = this._addJPGOrPNG(layer)
.catch(e => {
console.error(`Image located at ${this.url} could not be parsed as a ${this.imgFormat} file. Is the imgFormat specified correct?`);
return Promise.reject(e)
})
} else {
console.warn(`Image format: ${this.imgFormat} not supported`);
promise = Promise.reject();
};
// imgformat not defined we will try first supposing it is a fits file and then use the jpg heuristic
promise = self._addFITS(layer)
.catch(e => {
return self._addJPGOrPNG(layer)
.catch(e => {
console.error(`Image located at ${self.url} could not be parsed as jpg/png/tif image file. Aborting...`)
return Promise.reject(e);
})
})
}
promise = promise.then((imageParams) => {
self.formats = [self.imgFormat];
// There is at least one entry in imageParams
self.added = true;
self.setView(self.view);

View File

@@ -378,9 +378,11 @@ Utils.fetch = function(params) {
return fetch(request)
.then((resp) => {
if (params.dataType && params.dataType.includes('json')) {
return resp.json()
return resp.json();
} else if (params.dataType && params.dataType.includes('blob')) {
return resp.blob()
return resp.blob();
} else if (params.dataType && params.dataType.includes('readableStream')) {
return Promise.resolve(resp.body);
} else {
return resp.text()
}

346
src/js/libs/avm.js Normal file
View File

@@ -0,0 +1,346 @@
/*
* Javascript AVM/XMP Reader 0.1.3
* Copyright (c) 2010 Stuart Lowe http://www.strudel.org.uk/
* Astronomy Visualization Metadata (AVM) is defined at:
* http://www.virtualastronomy.org/avm_metadata.php
*
* Licensed under the MPL http://www.mozilla.org/MPL/MPL-1.1.txt
*
* S
*/
export let AVM = (function() {
function AVM(input) {
if (input instanceof HTMLImageElement) {
this.img = input;
} else if (input instanceof ArrayBuffer) {
this.img = input;
} else {
// suppose that input is a string
this.id = (input) ? input : "";
this.img = { complete: false };
}
this.xmp = ""; // Will hold the XMP string (for test purposes)
this.wcsdata = false;
this.AVMdefinedTags = {
'Creator':'photoshop:Source',
'CreatorURL':'Iptc4xmpCore:CiUrlWork',
'Contact.Name':'dc:creator',
'Contact.Email':'Iptc4xmpCore:CiEmailWork',
'Contact.Telephone':'Iptc4xmpCore:CiTelWork',
'Contact.Address':'Iptc4xmpCore:CiAdrExtadr',
'Contact.City':'Iptc4xmpCore:CiAdrCity',
'Contact.StateProvince':'Iptc4xmpCore:CiAdrRegion',
'Contact.PostalCode':'Iptc4xmpCore:CiAdrPcode',
'Contact.Country':'Iptc4xmpCore:CiAdrCtry',
'Rights':'xapRights:UsageTerms',
'Title':'dc:title',
'Headline':'photoshop:Headline',
'Description':'dc:description',
'Subject.Category':'avm:Subject.Category',
'Subject.Name':'dc:subject',
'Distance':'avm:Distance',
'Distance.Notes':'avm:Distance.Notes',
'ReferenceURL':'avm:ReferenceURL',
'Credit':'photoshop:Credit',
'Date':'photoshop:DateCreated',
'ID':'avm:ID',
'Type':'avm:Type',
'Image.ProductQuality':'avm:Image.ProductQuality',
'Facility':'avm:Facility',
'Instrument':'avm:Instrument',
'Spectral.ColorAssignment':'avm:Spectral.ColorAssignment',
'Spectral.Band':'avm:Spectral.Band',
'Spectral.Bandpass':'avm:Spectral.Bandpass',
'Spectral.CentralWavelength':'avm:Spectral.CentralWavelength',
'Spectral.Notes':'avm:Spectral.Notes',
'Temporal.StartTime':'avm:Temporal.StartTime',
'Temporal.IntegrationTime':'avm:Temporal.IntegrationTime',
'DatasetID':'avm:DatasetID',
'Spatial.CoordinateFrame':'avm:Spatial.CoordinateFrame',
'Spatial.Equinox':'avm:Spatial.Equinox',
'Spatial.ReferenceValue':'avm:Spatial.ReferenceValue',
'Spatial.ReferenceDimension':'avm:Spatial.ReferenceDimension',
'Spatial.ReferencePixel':'avm:Spatial.ReferencePixel',
'Spatial.Scale':'avm:Spatial.Scale',
'Spatial.Rotation':'avm:Spatial.Rotation',
'Spatial.CoordsystemProjection':'avm:Spatial.CoordsystemProjection',
'Spatial.Quality':'avm:Spatial.Quality',
'Spatial.Notes':'avm:Spatial.Notes',
'Spatial.FITSheader':'avm:Spatial.FITSheader',
'Spatial.CDMatrix':'avm:Spatial.CDMatrix',
'Publisher':'avm:Publisher',
'PublisherID':'avm:PublisherID',
'ResourceID':'avm:ResourceID',
'ResourceURL':'avm:ResourceURL',
'RelatedResources':'avm:RelatedResources',
'MetadataDate':'avm:MetadataDate',
'MetadataVersion':'avm:MetadataVersion'
}
}
AVM.prototype.load = function(fnCallback) {
if(!this.img && this.id) {
this.img = document.getElementById(this.id);
}
if (this.img instanceof ArrayBuffer) {
this.getData(fnCallback);
return;
}
if(!this.img.complete) {
var _obj = this;
addEvent(this.img, "load",
function() {
_obj.getData(fnCallback);
}
);
} else {
this.getData(fnCallback);
}
}
AVM.prototype.getData = function(fnCallback){
if(!this.imageHasData()){
this.getImageData(this.img, fnCallback);
}else{
if(typeof fnCallback=="function") fnCallback(this);
}
return true;
}
AVM.prototype.getImageData = function(oImg, fnCallback) {
var _obj = this;
const findAVM = (arrayBuffer) => {
const view = new DataView(arrayBuffer);
var oAVM = _obj.findAVMinJPEG(view);
_obj.wcs = oAVM || {};
_obj.wcsdata = _obj.wcs !== undefined && Object.keys(_obj.wcs).length > 0;
if (typeof fnCallback=="function") fnCallback(_obj);
};
if (oImg instanceof ArrayBuffer) {
findAVM(oImg)
} else {
let reqwst = new Request(oImg.src, {
method: 'GET',
})
fetch(reqwst)
.then((resp) => resp.arrayBuffer())
.then(arrayBuffer => {
findAVM(arrayBuffer)
})
}
}
function addEvent(oElement, strEvent, fncHandler){
if (oElement.addEventListener) oElement.addEventListener(strEvent, fncHandler, false);
else if (oElement.attachEvent) oElement.attachEvent("on" + strEvent, fncHandler);
}
AVM.prototype.imageHasData = function() {
return (this.img.wcsdata);
}
AVM.prototype.findAVMinJPEG = function(oFile) {
if (oFile.getUint8(0) != 0xFF || oFile.getUint8(1) != 0xD8) return false; // not a valid jpeg
var iOffset = 2;
var iLength = oFile.byteLength;
while (iOffset < iLength) {
if (oFile.getUint8(iOffset) != 0xFF) return false; // not a valid marker, something is wrong
var iMarker = oFile.getUint8(iOffset+1);
// we could implement handling for other markers here,
// but we're only looking for 0xFFE1 for AVM data
if (iMarker == 22400) {
return this.readAVMDataAsWCS(oFile, iOffset + 4, oFile.getUint16(iOffset+2, false)-2);
//iOffset += 2 + oFile.getUint16(iOffset+2, false);
} else if (iMarker == 225) {
// 0xE1 = Application-specific 1 (for AVM)
return this.readAVMDataAsWCS(oFile, iOffset + 4, oFile.getUint16(iOffset+2, false)-2);
} else {
iOffset += 2 + oFile.getUint16(iOffset+2, false);
}
}
}
AVM.prototype.readAVMDataAsWCS = function(oFile) {
var tags = undefined;
let wcs = {};
this.xmp = this.readXMP(oFile);
if (this.xmp) {
tags = this.readAVM(this.xmp);
if (tags) {
this.tags = tags;
let unwindTag = (tag) => {
if (Array.isArray(tag)) {
return tag[0]
} else {
return tag;
}
}
wcs.CTYPE1 = unwindTag(tags['Spatial.CoordinateFrame']) === 'ICRS' ? 'RA---' : 'GLON-';
wcs.CTYPE1 += unwindTag(tags['Spatial.CoordsystemProjection']);
wcs.CTYPE2 = unwindTag(tags['Spatial.CoordinateFrame']) === 'ICRS' ? 'DEC--' : 'GLAT-';
wcs.CTYPE2 += unwindTag(tags['Spatial.CoordsystemProjection']);
if (unwindTag(tags['Spatial.Equinox']))
wcs.EQUINOX = +unwindTag(tags['Spatial.Equinox']);
wcs.NAXIS1 = tags['Spatial.ReferenceDimension'] && +tags['Spatial.ReferenceDimension'][0];
wcs.NAXIS2 = tags['Spatial.ReferenceDimension'] && +tags['Spatial.ReferenceDimension'][1];
if (tags['Spatial.CDMatrix']) {
console.warn("Spatial.CDMatrix is deprecated in favor of Spatial.Scale + Spatial.Rotation");
wcs.CD1_1 = +tags['Spatial.CDMatrix'][0];
wcs.CD1_2 = +tags['Spatial.CDMatrix'][1];
wcs.CD2_1 = +tags['Spatial.CDMatrix'][2];
wcs.CD2_2 = +tags['Spatial.CDMatrix'][3];
} else {
wcs.CDELT1 = tags['Spatial.Scale'] && +tags['Spatial.Scale'][0];
wcs.CDELT2 = tags['Spatial.Scale'] && +tags['Spatial.Scale'][1];
if (unwindTag(tags['Spatial.Rotation']) !== undefined) {
wcs.CROTA2 = +unwindTag(tags['Spatial.Rotation']);
}
}
wcs.CRPIX1 = tags['Spatial.ReferencePixel'] && +tags['Spatial.ReferencePixel'][0];
wcs.CRPIX2 = tags['Spatial.ReferencePixel'] && +tags['Spatial.ReferencePixel'][1];
wcs.CRVAL1 = tags['Spatial.ReferenceValue'] && +tags['Spatial.ReferenceValue'][0];
wcs.CRVAL2 = tags['Spatial.ReferenceValue'] && +tags['Spatial.ReferenceValue'][1];
} else {
var equalReached = false;
for(var key of ['NAXIS1', 'NAXIS2', 'CTYPE1', 'CTYPE2', 'CRPIX1', 'CRPIX2', 'CRVAL1', 'CRVAL2', 'LONPOLE', 'LATPOLE', 'CDELT1', 'CDELT2', 'PC1_1', 'PC2_2', 'PC1_2', 'PC2_1', 'CD1_1', 'CD2_2', 'CD1_2', 'CD2_1']) {
equalReached = false;
// try to read directly the WCS
let beginCard = this.xmp.slice(this.xmp.indexOf(key));
let values = beginCard.split(" ");
for (var v of values) {
if (equalReached && v !== "") {
wcs[key] = parseFloat(v);
if (Number.isNaN(wcs[key])) {
if (v[0] === "'" || v[0] === "\"") {
v = v.slice(1, v.length - 1)
}
wcs[key] = v;
}
break;
}
if (v === "=") {
equalReached = true;
}
}
}
}
}
return wcs;
}
AVM.prototype.readXMP = function(oFile) {
var iEntries = oFile.byteLength;
var prev_n_hex = '';
var record = false;
var recordn = 0;
// Find the XMP packet - 8 bit encoding (UTF-8)
// see page 34 of http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf
var xmpStr = '0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20 0x62 0x65 0x67 0x69 0x6E 0x3D ';
var xmpBytes = 14;
var byteStr = '';
var iEntryOffset = -1;
// Here we want to search for the XMP packet starting string
// There is probably a more efficient way to search for a byte string
for (var i=0;i<iEntries;i++) {
var n = oFile.getUint8(i);
var n_hex = n.toString(16).toUpperCase();
if(n_hex.length == 1) n_hex = "0x0"+n_hex;
if(n_hex.length == 2) n_hex = "0x"+n_hex;
if(prev_n_hex == "0x3C" && n_hex == "0x3F"){
record = true;
recordn = xmpBytes;
byteStr = '0x3C ';
}
if(record){
byteStr += n_hex+' ';
recordn--;
if(recordn < 0){
if(byteStr == xmpStr){
var iEntryOffset = i-xmpBytes-1;
break;
}
record = false;
}
}
prev_n_hex = n_hex;
}
if(iEntryOffset >= 0){
var str = '';
var i = iEntryOffset;
while(str.indexOf('</x:xmpmeta>') < 0 && i < (iEntryOffset+20000)){
str += String.fromCharCode(oFile.getUint8(i));
i++;
}
return str;
}
}
AVM.prototype.readAVM = function(str) {
var tags = undefined;
if(str.indexOf('xmlns:avm') >= 0){
tags = {}
for (var keyname in this.AVMdefinedTags) {
var key = this.AVMdefinedTags[keyname];
key.toLowerCase();
var start = str.indexOf(key)+key.length+2;
var final = str.indexOf('"',start);
// Find out what the character is after the key
var char = str.substring(start-2,start-1);
if(char == "="){
tags[keyname] = str.substring(start,final);
}else if(char == ">"){
final = str.indexOf('</'+key+'>',start);
// Parse out the HTML tags and build an array of the resulting values
var tmps = str.substring(start-1,final);
var tmparr = new Array(0);
var tmpstr = tmps.replace(/[\n\r]/g,"");
tmpstr = tmpstr.replace(/ +/g," ");
tmparr = tmpstr.split(/ ?<\/?[^>]+> ?/g);
var newarr = new Array(0);
for(var i = 0;i<tmparr.length;i++){
if(tmparr[i].length > 0) newarr.push(tmparr[i]);
}
tags[keyname] = newarr;
}
}
}
return tags;
}
return AVM;
})();