mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-24 20:10:30 -08:00
Compare commits
4 Commits
feat-color
...
avm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d060c12e9 | ||
|
|
dd55f8904b | ||
|
|
2c0afd8b84 | ||
|
|
245030fb0a |
@@ -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>
|
||||
|
||||
27
examples/al-image-with-AVM-tags.html
Normal file
27
examples/al-image-with-AVM-tags.html
Normal 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>
|
||||
@@ -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.
|
||||
},
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
290
src/js/Image.js
290
src/js/Image.js
@@ -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);
|
||||
|
||||
@@ -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
346
src/js/libs/avm.js
Normal 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;
|
||||
})();
|
||||
Reference in New Issue
Block a user