Compare commits

..

5 Commits

Author SHA1 Message Date
Matthieu Baumann
66d3b8dcce first HiPS3D commit 2025-07-01 20:07:14 +02:00
Matthieu Baumann
82580c8104 use fitsrs 0.3.4 fixed version 2025-06-27 17:20:43 +02:00
Matthieu Baumann
bad2211ff0 clippy + fmt 2025-06-27 11:40:33 +02:00
Matthieu Baumann
1fc515e9e9 fix big FITS orientation. new feat: subdivide big PNG/JPG image as well 2025-06-26 23:18:00 +02:00
Matthieu Baumann
d3c869edb4 Update to the new version of fitsrs
* Async code is replaced by sync one. It does not take that much of time to convert the js blob to wasm memory for 1 or 2 GB fits files and the code is much more simplier to write
* GLSL/rust refac has been done to only call fitsrs from only one place in the code
* Raw fits data unit is directly given to the GPU. Then the GPU perform the big to little endian conversion on the fly
* impl GZIP fits support #241
* fix #293
2025-06-17 19:54:35 +02:00
89 changed files with 2012 additions and 4536 deletions

View File

@@ -15,13 +15,7 @@ A new [API technical documentation](https://cds-astro.github.io/aladin-lite/) is
[![API Documentation](https://img.shields.io/badge/API-documentation-blue.svg)](https://cds-astro.github.io/aladin-lite)
[![Release page](https://img.shields.io/badge/Release-download-yellow.svg)](https://aladin.cds.unistra.fr/AladinLite/doc/release/)
Try Aladin Lite [here](https://aladin.u-strasbg.fr/AladinLite).
Aladin Lite is made possible thanks to pure Rust core libraries:
* [cdshealpix](https://github.com/cds-astro/cds-healpix-rust) - for HEALPix projection and unprojection to/from sky coordinates
* [mapproj](https://github.com/cds-astro/cds-mapproj-rust) - for computing (un)projections described by a WCS
* [fitsrs](https://github.com/cds-astro/fitsrs) - for reading and parsing FITS images
* [moc](https://github.com/cds-astro/cds-moc-rust) - for parsing, manipulating, and serializing multi-order HEALPix coverage maps
Aladin Lite is available [at this link](https://aladin.u-strasbg.fr/AladinLite).
## Running & editable JS examples
@@ -108,15 +102,14 @@ Aladin Lite can be imported with:
* [X] FITS images support
* [X] WCS parsing, displaying an (JPEG/PNG) image in aladin lite view
* [X] Display customized shapes (e.g. proper motions) from astronomical catalog data
* [X] AVM tags parsing support inside JPEG
* [X] AVM tags parsing support
* [X] Easy sharing of current « view »
* [ ] All VOTable serializations
* [ ] FITS tables
* [X] Creating HiPS instance from an URL
* [X] Local HiPS loading
* [X] Multiple mirrors handling for HiPS tile retrival
* [X] HiPS cube
* [ ] HiPS3D
* [ ] HiPS cube
## Licence

View File

@@ -26,17 +26,8 @@
limit: 1000,
//orderBy: 'nb_ref',
onClick: 'showTable',
onlyFootprints: false,
color: (s) => {
let coo = A.coo();
coo.parse(s.data['RAJ2000'] + ' ' + s.data['DEJ2000'])
let a = (0.1 * Math.pow(10, +s.data.logD25)) / 60;
let b = (1.0 / Math.pow(10, +s.data.logR25)) * a
return `rgb(${s.data["logR25"]*255.0}, ${s.data["logR25"]*255.0}, 255)`
},
hoverColor: 'red',
color: 'yellow',
hoverColor: 'blue',
shape: (s) => {
let coo = A.coo();
coo.parse(s.data['RAJ2000'] + ' ' + s.data['DEJ2000'])

View File

@@ -31,21 +31,15 @@
hoverColor: 'yellow',
selectionColor: 'white',
// Footprint associated to sources
color: (s) => {
// discard drawing a vector for big pm
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
if (totalPmSquared > 6) {
return;
}
return rainbowColorMap((totalPmSquared - 2.5) / 2)
},
shape: (s) => {
// discard drawing a vector for big pm
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
if (totalPmSquared > 6) {
return;
}
let color = rainbowColorMap((totalPmSquared - 2.5) / 2)
// Compute the mean of pm over the catalog sources
if (!pmraMean || !pmdecMean) {
pmraMean = 0, pmdecMean = 0;
@@ -68,24 +62,13 @@
s.dec,
s.ra + dra,
s.dec + ddec,
{color}
)
}
},
() => {
aladin.addCatalog(pmCat);
pmCat.select((s) => {
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
if (totalPmSquared > 6) {
return false;
}
return totalPmSquared < 3.0;
});
});
aladin.addCatalog(pmCat);
});
function rainbowColorMap(value) {
// Ensure value is within range [0, 1]
value = Math.max(0, Math.min(1, value));

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, height=device-height, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 500px"></div>
<script type="text/javascript" src="./../dist/aladin.umd.cjs" charset="utf-8"></script>
<script type="text/javascript">
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', fov: 1.0, target: 'M 20', showContextMenu: true});
// customize share URL function
aladin.customizeShareURLFunction(() => {return 'https://sky.esa.int/esasky/?target=' + aladin.getRaDec()[0] + '%20' + aladin.getRaDec()[1] + '&fov=' + aladin.getFoV()[0]})
});
</script>
</body>
</html>

View File

@@ -11,7 +11,7 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {cooFrame: "icrs", log: false, backgroundColor: 'rgba(0, 0, 0, 255)'});
aladin = A.aladin('#aladin-lite-div', {cooFrame: "icrs", log: false, backgroundColor: 'red'});
aladin.displayFITS(
//'https://fits.gsfc.nasa.gov/samples/FOCx38i0101t_c0f.fits', // url of the fits file

View File

@@ -39,7 +39,7 @@ Image Opacity: <br/> <input id="slider" type="range" value=1 min=0 max=1 step=0.
//let fits = aladin.displayFITS('http://goldmine.mib.infn.it/data//B/fits/A04_VC1316_ooooog.fits', 'overlay');
let jpg = aladin.displayJPG(
// the JPG to transform to HiPS
'https://owncloud.tuebingen.mpg.de/index.php/s/sdxfNgcEaaXoBp7/download/nightskycam3_2025_08_07_05_17_30_healpix1024_red.fits',
'https://noirlab.edu/public/media/archives/images/large/noirlab1912a.jpg',
// no options
{
transparency: 1.0,

View File

@@ -1,62 +0,0 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 768px; height: 512px"></div>
<script>let aladin; let hips;</script>
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
aladin = A.aladin(
'#aladin-lite-div',
{
showSimbadPointerControl: true,
projection: 'AIT', // set a projection
fov: 8.0, // initial field of view in degrees
target: '00 42 21.37 +41 07 29.8', // initial target
cooFrame: 'icrs', // set galactic frame
reticleColor: '#ff89ff', // change reticle color
showContextMenu: true,
showFrame: true,
showZoomControl:true,
showSettingsControl:true,
fullScreen: true,
samp: true,
}
);
hips = aladin.newImageSurvey("http://alasky.cds.unistra.fr/HIPS3D/GalfaHI", {
successCallback: (hips) => {
//hips.setFrequency({value: 6.374279333565797E-7, unit: "m"}) // GALFA
}
});
// compressed https://alasky.cds.unistra.fr/test-compression-cubes/DHIGLS/
//hips = aladin.newImageSurvey("http://alasky.cds.unistra.fr/DHIGLS");
//hips = aladin.newImageSurvey("https://alasky.cds.unistra.fr/MUSE3D");
// http://alasky.cds.unistra.fr/LGLBSHI
aladin.setImageLayer(hips)
//hips.setFrequency({value: emMin + delta * i, unit: "m"})
//hips.setFrequency({value: 6.374279333565797E-7, unit: "m"}) // MUSE
//hips.setFrequency({value: 0.21101690259115785, unit: "m"}) // DGHILG
/*let id;
aladin.on("zoomChanged", () => {
if (id)
clearTimeout(id);
id = setTimeout(() => {
console.log("wheel stopped, new cone search here")
}, 500);
})*/
});
</script>
<style>
.aladin-cat-browser-box {
width: 600px;
}
</style>
</body>
</html>

View File

@@ -30,14 +30,9 @@
}
);
hips = aladin.newImageSurvey("https://alasky.cds.unistra.fr/GALFAHI/GALFAHI-Narrow-DR2");
hips = aladin.newImageSurvey("https://alasky.cds.unistra.fr/GALFAHI/GALFAHI-Narrow-DR2/");
aladin.setImageLayer(hips)
setTimeout(() => {
hips.setSliceNumber(100)
}, 1000)
/*let id;
aladin.on("zoomChanged", () => {
if (id)

View File

@@ -18,12 +18,12 @@ futures = "0.3.12"
js-sys = "0.3.47"
wasm-bindgen-futures = "0.4.20"
cgmath = "*"
# url-lite = "0.1.0"
url-lite = "0.1.0"
serde_json = "1.0.104"
serde-wasm-bindgen = "0.5"
enum_dispatch = "0.3.8"
wasm-bindgen = "=0.2.92"
#wasm-streams = "0.3.0"
wasm-streams = "0.3.0"
async-channel = "1.8.0"
mapproj = "0.3.0"
fitsrs = "0.3.4"
@@ -50,8 +50,7 @@ version = "0.7.3"
[dependencies.moclib]
package = "moc"
git = "https://github.com/cds-astro/cds-moc-rust"
branch = "main"
version = "0.18.0"
[dependencies.serde]
version = "^1.0.183"
@@ -65,7 +64,7 @@ path = "./al-api"
[dependencies.web-sys]
version = "0.3.56"
features = [ "console", "CssStyleDeclaration", "Document", "Element", "HtmlCollection", "CustomEvent", "CustomEventInit", "HtmlElement", "HtmlImageElement", "HtmlCanvasElement", "Blob", "ImageBitmap", "ImageData", "CanvasRenderingContext2d", "WebGlBuffer", "WebGlContextAttributes", "WebGlFramebuffer", "WebGlProgram", "WebGlShader", "WebGlUniformLocation", "WebGlTexture", "WebGlActiveInfo", "Headers", "Window", "Request", "RequestInit", "RequestMode", "RequestCredentials", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList",]
features = [ "console", "CssStyleDeclaration", "Document", "Element", "HtmlCollection", "HtmlElement", "HtmlImageElement", "HtmlCanvasElement", "Blob", "ImageBitmap", "ImageData", "CanvasRenderingContext2d", "WebGlBuffer", "WebGlContextAttributes", "WebGlFramebuffer", "WebGlProgram", "WebGlShader", "WebGlUniformLocation", "WebGlTexture", "WebGlActiveInfo", "Headers", "Window", "Request", "RequestInit", "RequestMode", "RequestCredentials", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList",]
[dev-dependencies.image-decoder]
package = "image"

View File

@@ -55,11 +55,6 @@ pub struct HiPSProperties {
hips_order_freq: Option<u8>,
hips_tile_depth: Option<u8>,
/// Start of spectral coordinates (in meters)
em_min: Option<f32>,
/// End of spectral coordinates (in meters)
em_max: Option<f32>,
// Parametrable by the user
#[allow(unused)]
min_cutout: Option<f32>,
@@ -163,23 +158,12 @@ impl HiPSProperties {
pub fn get_request_mode(&self) -> &str {
&self.request_mode
}
#[inline(always)]
pub fn get_em_min(&self) -> Option<f32> {
self.em_min
}
#[inline(always)]
pub fn get_em_max(&self) -> Option<f32> {
self.em_max
}
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum ImageExt {
#[serde(alias = "fits", alias = "fits.fz")]
Fits,
Jpeg,
Png,
@@ -190,7 +174,6 @@ pub enum ImageExt {
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum DataproductType {
#[serde(rename = "spectral-cube")]
SpectralCube,
Image,
Cube,

View File

@@ -7,14 +7,14 @@ edition = "2018"
[dependencies]
js-sys = "0.3.47"
cgmath = "*"
#jpeg-decoder = "0.3.0"
#png = "0.17.6"
jpeg-decoder = "0.3.0"
png = "0.17.6"
fitsrs = "0.3.4"
al-api = { path = "../al-api" }
serde = { version = "^1.0.59", features = ["derive"] }
serde_json = "1.0"
serde-wasm-bindgen = "0.4"
# wasm-streams = "0.3.0"
wasm-streams = "0.3.0"
futures = "0.3.25"
colorgrad = "0.6.2"
wasm-bindgen = "0.2.92"

View File

@@ -35,7 +35,7 @@ where
Ok(())
}
fn get_size(&self) -> (u32, u32, u32) {
(self.image.width(), self.image.height(), 1)
fn get_size(&self) -> (u32, u32) {
(self.image.width(), self.image.height())
}
}

View File

@@ -43,7 +43,7 @@ where
Ok(())
}
fn get_size(&self) -> (u32, u32, u32) {
(self.canvas.width(), self.canvas.height(), 1)
fn get_size(&self) -> (u32, u32) {
(self.canvas.width(), self.canvas.height())
}
}

View File

@@ -2,27 +2,24 @@ use crate::texture::format::TextureFormat;
use crate::texture::format::R8U;
use cgmath::Vector3;
use fitsrs::card::Value;
use fitsrs::hdu::header::extension::image::Image as XImage;
use fitsrs::hdu::header::Bitpix;
use fitsrs::hdu::header::Header;
use fitsrs::hdu::header::Xtension;
use fitsrs::WCS;
use fitsrs::{Fits, HDU};
use std::fmt::Debug;
use std::io::Cursor;
use std::ops::Range;
use wasm_bindgen::JsValue;
#[derive(Debug)]
pub struct FitsImage<'a> {
// Margin values for HiPS3D cubic tiles
pub trim1: u32,
pub trim2: u32,
pub trim3: u32,
// Image/cube size
// get a reference to the header
pub header: Header<XImage>,
// image size
pub width: u32,
pub height: u32,
pub depth: u32,
// Bitpix
// bitpix
pub bitpix: Bitpix,
// 1.0 by default
pub bscale: f32,
@@ -32,20 +29,10 @@ pub struct FitsImage<'a> {
pub blank: Option<f32>,
// optional wcs
pub wcs: Option<WCS>,
// bytes offset where the data bytes are located inside the fits
pub data_byte_offset: Range<usize>,
// raw bytes of the data image (in Big-Endian)
pub raw_bytes: &'a [u8],
}
fn parse_keyword_as_number<X: Xtension + Debug>(header: &Header<X>, keyword: &str) -> Option<f32> {
match header.get(keyword) {
Some(Value::Integer { value, .. }) => Some(*value as f32),
Some(Value::Float { value, .. }) => Some(*value as f32),
_ => None,
}
}
impl<'a> FitsImage<'a> {
/// Get all the hdu images from a fits file
pub fn from_raw_bytes(bytes: &'a [u8]) -> Result<Vec<Self>, JsValue> {
@@ -66,28 +53,35 @@ impl<'a> FitsImage<'a> {
let header = hdu.get_header();
let bscale = parse_keyword_as_number(header, "BSCALE").unwrap_or(1.0);
let bzero = parse_keyword_as_number(header, "BZERO").unwrap_or(0.0);
let blank = parse_keyword_as_number(header, "BLANK");
let bscale = match header.get("BSCALE") {
Some(Value::Integer { value, .. }) => *value as f32,
Some(Value::Float { value, .. }) => *value as f32,
_ => 1.0,
};
let trim1 = parse_keyword_as_number(header, "TRIM1").unwrap_or(0.0) as u32;
let trim2 = parse_keyword_as_number(header, "TRIM2").unwrap_or(0.0) as u32;
let trim3 = parse_keyword_as_number(header, "TRIM3").unwrap_or(0.0) as u32;
let bzero = match header.get("BZERO") {
Some(Value::Integer { value, .. }) => *value as f32,
Some(Value::Float { value, .. }) => *value as f32,
_ => 0.0,
};
let bitpix = hdu.get_header().get_xtension().get_bitpix();
let blank = match header.get("BLANK") {
Some(Value::Integer { value, .. }) => Some(*value as f32),
Some(Value::Float { value, .. }) => Some(*value as f32),
_ => None,
};
let off = hdu.get_data_unit_byte_offset() as usize;
let len = hdu.get_data_unit_byte_size() as usize;
let data_byte_offset = off..(off + len);
let raw_bytes = &bytes[data_byte_offset.clone()];
let raw_bytes = &bytes[off..(off + len)];
let bitpix = hdu.get_header().get_xtension().get_bitpix();
let wcs = hdu.wcs().ok();
images.push(Self {
trim1,
trim2,
trim3,
header: hdu.get_header().clone(),
width: width as u32,
height: height as u32,
depth,
@@ -96,7 +90,6 @@ impl<'a> FitsImage<'a> {
wcs,
bzero,
blank,
data_byte_offset,
raw_bytes,
});
}
@@ -114,7 +107,6 @@ impl<'a> FitsImage<'a> {
}
use crate::{image::Image, texture::Tex3D};
use std::convert::TryInto;
impl Image for FitsImage<'_> {
fn insert_into_3d_texture<T: Tex3D>(
&self,
@@ -123,45 +115,11 @@ impl Image for FitsImage<'_> {
// An offset to write the image in the texture array
offset: &Vector3<i32>,
) -> Result<(), JsValue> {
let view = unsafe {
match self.bitpix {
Bitpix::I64 => {
// convert to i64 first
let new_bytes: Vec<_> = self
.raw_bytes
.chunks_exact(8)
.flat_map(|chunk| {
let bytes: [u8; 8] = chunk.try_into().unwrap();
let value = i64::from_be_bytes(bytes);
(value as i32).to_be_bytes()
})
.collect();
R8U::view(&new_bytes)
}
Bitpix::F64 => {
let new_bytes: Vec<_> = self
.raw_bytes
.chunks_exact(8)
.flat_map(|chunk| {
let bytes: [u8; 8] = chunk.try_into().unwrap();
let value = f64::from_be_bytes(bytes);
(value as f32).to_be_bytes()
})
.collect();
R8U::view(&new_bytes)
}
_ => R8U::view(self.raw_bytes),
}
};
let view = unsafe { R8U::view(self.raw_bytes) };
textures.tex_sub_image_3d_with_opt_array_buffer_view(
offset.x + self.trim1 as i32,
offset.y + self.trim2 as i32,
offset.z + self.trim3 as i32,
offset.x,
offset.y,
offset.z,
self.width as i32,
self.height as i32,
self.depth as i32,
@@ -171,8 +129,7 @@ impl Image for FitsImage<'_> {
Ok(())
}
fn get_size(&self) -> (u32, u32, u32) {
// The true image size is given by ONAXISi keywords
(self.width, self.height, self.depth)
fn get_size(&self) -> (u32, u32) {
(self.width, self.height)
}
}

View File

@@ -1,7 +1,7 @@
/* ------------------------------------------------------ */
#[derive(Debug)]
pub struct HTMLImage<F> {
pub image: web_sys::HtmlImageElement,
image: web_sys::HtmlImageElement,
format: std::marker::PhantomData<F>,
}
@@ -15,10 +15,6 @@ where
format: std::marker::PhantomData,
}
}
pub fn element(&self) -> &web_sys::HtmlImageElement {
&self.image
}
}
use crate::image::Image;
@@ -47,7 +43,7 @@ where
Ok(())
}
fn get_size(&self) -> (u32, u32, u32) {
(self.image.width(), self.image.height(), 1)
fn get_size(&self) -> (u32, u32) {
(self.image.width(), self.image.height())
}
}

View File

@@ -191,7 +191,7 @@ pub trait Image {
offset: &Vector3<i32>,
) -> Result<(), JsValue>;
fn get_size(&self) -> (u32, u32, u32);
fn get_size(&self) -> (u32, u32);
}
impl<I> Image for &I
@@ -212,7 +212,7 @@ where
}
#[inline]
fn get_size(&self) -> (u32, u32, u32) {
fn get_size(&self) -> (u32, u32) {
let image = &**self;
image.get_size()
}
@@ -237,7 +237,7 @@ where
}
#[inline]
fn get_size(&self) -> (u32, u32, u32) {
fn get_size(&self) -> (u32, u32) {
let image = &**self;
image.get_size()
}
@@ -250,7 +250,7 @@ use crate::texture::Tex3D;
pub enum ImageType {
FitsRawBytes {
raw_bytes: js_sys::Uint8Array,
size: (u32, u32, u32),
size: (u32, u32),
},
Canvas {
canvas: Canvas<RGBA8U>,
@@ -311,10 +311,10 @@ impl Image for ImageType {
ImageType::Canvas { canvas } => canvas.insert_into_3d_texture(textures, offset)?,
ImageType::ImageRgba8u { image } => image.insert_into_3d_texture(textures, offset)?,
ImageType::ImageRgb8u { image } => image.insert_into_3d_texture(textures, offset)?,
ImageType::HTMLImageRgba8u { image, .. } => {
ImageType::HTMLImageRgba8u { image } => {
image.insert_into_3d_texture(textures, offset)?
}
ImageType::HTMLImageRgb8u { image, .. } => {
ImageType::HTMLImageRgb8u { image } => {
image.insert_into_3d_texture(textures, offset)?
}
ImageType::RawRgb8u { image } => image.insert_into_3d_texture(textures, offset)?,
@@ -328,7 +328,7 @@ impl Image for ImageType {
Ok(())
}
fn get_size(&self) -> (u32, u32, u32) {
fn get_size(&self) -> (u32, u32) {
match self {
ImageType::FitsRawBytes { size, .. } => *size,
ImageType::Canvas { canvas } => canvas.get_size(),

View File

@@ -8,8 +8,8 @@ pub struct ImageBuffer<T>
where
T: TextureFormat,
{
pub data: Box<[<<T as TextureFormat>::P as Pixel>::Item]>,
pub size: (u32, u32, u32),
pub data: Vec<<<T as TextureFormat>::P as Pixel>::Item>,
pub size: Vector2<i32>,
}
use crate::texture::format::Bytes;
@@ -26,22 +26,21 @@ where
T: TextureFormat,
{
pub fn new(
data: Box<[<<T as TextureFormat>::P as Pixel>::Item]>,
width: u32,
height: u32,
depth: u32,
data: Vec<<<T as TextureFormat>::P as Pixel>::Item>,
width: i32,
height: i32,
) -> Self {
let size_buf = width * height * depth * (T::NUM_CHANNELS as u32);
debug_assert!(size_buf == data.len() as u32);
let size_buf = width * height * (T::NUM_CHANNELS as i32);
debug_assert!(size_buf == data.len() as i32);
//let buf = <<T as ImageFormat>::P as Pixel>::Container::new(buf);
let size = (width, height, depth);
let size = Vector2::new(width, height);
Self { data, size }
}
pub fn from_encoded_raw_bytes(
raw_bytes: &[u8],
width: u32,
height: u32,
width: i32,
height: i32,
) -> Result<Self, JsValue> {
let mut decoded_bytes = match T::decode(raw_bytes).map_err(JsValue::from_str)? {
Bytes::Borrowed(bytes) => bytes.to_vec(),
@@ -56,33 +55,29 @@ where
std::mem::transmute::<Vec<u8>, Vec<<<T as TextureFormat>::P as Pixel>::Item>>(
decoded_bytes,
)
.into_boxed_slice()
};
Ok(Self::new(decoded_pixels, width, height, 1))
Ok(Self::new(decoded_pixels, width, height))
}
pub fn from_raw_bytes(mut raw_bytes: Vec<u8>, width: u32, height: u32) -> Self {
let size_buf = width * height * (std::mem::size_of::<T::P>() as u32);
debug_assert!(size_buf == raw_bytes.len() as u32);
pub fn from_raw_bytes(mut raw_bytes: Vec<u8>, width: i32, height: i32) -> Self {
let size_buf = width * height * (std::mem::size_of::<T::P>() as i32);
debug_assert!(size_buf == raw_bytes.len() as i32);
let decoded_pixels = unsafe {
raw_bytes.set_len(raw_bytes.len() / std::mem::size_of::<<T::P as Pixel>::Item>());
std::mem::transmute::<Vec<u8>, Vec<<T::P as Pixel>::Item>>(raw_bytes).into_boxed_slice()
std::mem::transmute::<Vec<u8>, Vec<<T::P as Pixel>::Item>>(raw_bytes)
};
Self::new(decoded_pixels, width, height, 1)
Self::new(decoded_pixels, width, height)
}
pub fn empty() -> Self {
let size = (0, 0, 0);
Self {
data: Box::new([]),
size,
}
let size = Vector2::new(0, 0);
Self { data: vec![], size }
}
pub fn allocate(pixel_fill: &T::P, width: u32, height: u32) -> ImageBuffer<T> {
pub fn allocate(pixel_fill: &T::P, width: i32, height: i32) -> ImageBuffer<T> {
let size_buf = ((width * height) as usize) * (T::NUM_CHANNELS);
let data = pixel_fill
@@ -91,10 +86,9 @@ where
.cloned()
.cycle()
.take(size_buf)
.collect::<Vec<_>>()
.into_boxed_slice();
.collect::<Vec<_>>();
ImageBuffer::<T>::new(data, width, height, 1)
ImageBuffer::<T>::new(data, width, height)
}
pub fn tex_sub(&mut self, src: &Self, s: &ImageBufferView, d: &ImageBufferView) {
@@ -103,8 +97,8 @@ where
for ix in s.x..(s.x + s.w) {
for iy in s.y..(s.y + s.h) {
let s_idx = ((iy * src.width() as i32) + ix) as usize;
let d_idx = ((di * self.width() as i32) + dj) as usize;
let s_idx = (iy * src.width() + ix) as usize;
let d_idx = (di * self.width() + dj) as usize;
for i in 0..T::NUM_CHANNELS {
let si = s_idx * T::NUM_CHANNELS + i;
@@ -130,12 +124,12 @@ where
&self.data
}
pub fn width(&self) -> u32 {
self.size.0
pub fn width(&self) -> i32 {
self.size.x
}
pub fn height(&self) -> u32 {
self.size.1
pub fn height(&self) -> i32 {
self.size.y
}
}
@@ -150,7 +144,7 @@ pub enum ImageBufferType {
}
use crate::image::{ArrayBuffer, Image};
use cgmath::Vector3;
use cgmath::{Vector2, Vector3};
impl<I> Image for ImageBuffer<I>
where
I: TextureFormat,
@@ -167,9 +161,9 @@ where
offset.x,
offset.y,
offset.z,
self.width() as i32,
self.height() as i32,
self.size.2 as i32,
self.width(),
self.height(),
1,
Some(js_array.as_ref()),
);
@@ -177,7 +171,7 @@ where
}
// The size of the image
fn get_size(&self) -> (u32, u32, u32) {
self.size
fn get_size(&self) -> (u32, u32) {
(self.size.x as u32, self.size.y as u32)
}
}

View File

@@ -1,8 +1,8 @@
extern crate futures;
//extern crate jpeg_decoder as jpeg;
//extern crate png;
extern crate jpeg_decoder as jpeg;
extern crate png;
extern crate serde_json;
//extern crate wasm_streams;
extern crate wasm_streams;
pub mod convert;
pub mod image;

View File

@@ -19,6 +19,7 @@ pub struct Texture3D {
texture: Option<WebGlTexture>,
metadata: Option<Rc<RefCell<Texture2DMeta>>>,
_depth: i32,
}
impl Texture3D {
@@ -61,6 +62,7 @@ impl Texture3D {
Ok(Texture3D {
texture,
gl: gl.clone(),
_depth: depth,
metadata,
})
}
@@ -69,7 +71,7 @@ impl Texture3D {
self.gl.generate_mipmap(WebGlRenderingCtx::TEXTURE_3D);
}
pub fn bind(&self) -> Texture3DBound<'_> {
pub fn bind(&self) -> Texture3DBound {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_3D, self.texture.as_ref());

View File

@@ -70,7 +70,7 @@ impl Texture2DArray {
self.gl.generate_mipmap(WebGlRenderingCtx::TEXTURE_2D_ARRAY);
}
pub fn bind(&self) -> Texture2DArrayBound<'_> {
pub fn bind(&self) -> Texture2DArrayBound {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_2D_ARRAY, self.texture.as_ref());

View File

@@ -43,14 +43,13 @@ impl TextureFormat for RGB8U {
const PIXEL_TYPE: PixelType = PixelType::RGB8U;
fn decode(_raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
todo!()
/*let mut decoder = jpeg::Decoder::new(raw_bytes);
fn decode(raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
let mut decoder = jpeg::Decoder::new(raw_bytes);
let bytes = decoder
.decode()
.map_err(|_| "Cannot decoder jpeg. This image may not be compressed.")?;
Ok(Bytes::Owned(bytes))*/
Ok(Bytes::Owned(bytes))
}
type ArrayBufferView = js_sys::Uint8Array;
@@ -73,15 +72,13 @@ impl TextureFormat for RGBA8U {
const PIXEL_TYPE: PixelType = PixelType::RGBA8U;
fn decode(_raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
/*let mut decoder = jpeg::Decoder::new(raw_bytes);
fn decode(raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
let mut decoder = jpeg::Decoder::new(raw_bytes);
let bytes = decoder
.decode()
.map_err(|_| "Cannot decoder png. This image may not be compressed.")?;
Ok(Bytes::Owned(bytes))
*/
todo!()
}
type ArrayBufferView = js_sys::Uint8Array;

View File

@@ -295,7 +295,7 @@ impl Texture2D {
self
}
pub fn bind(&self) -> Texture2DBound<'_> {
pub fn bind(&self) -> Texture2DBound {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_2D, self.texture.as_ref());

View File

@@ -49,7 +49,7 @@ fn read_shader<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<String> {
let shader_src = std::io::BufReader::new(file)
.lines()
.map_while(Result::ok)
.filter_map(|l| {
.map(|l| {
if l.starts_with("#include") {
let incl_file_names: Vec<_> = l.split_terminator(&[';', ' '][..]).collect();
let incl_file_name_rel = incl_file_names[1];
@@ -57,12 +57,9 @@ fn read_shader<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<String> {
println!("{}", incl_file_name.to_string_lossy());
Some(read_shader(incl_file_name.to_str().unwrap()).unwrap())
} else if l.trim_start().starts_with("//") {
// comment
None
read_shader(incl_file_name.to_str().unwrap()).unwrap()
} else {
Some(l)
l
}
})
.collect::<Vec<_>>()

View File

@@ -1,12 +1,9 @@
use crate::browser_support::BrowserFeaturesSupport;
use crate::downloader::request::moc::MOCRequest;
use crate::math::angle::ToAngle;
use crate::math::spectra::Freq;
use crate::renderable::hips::HiPS;
use crate::renderable::image::Image;
use crate::renderable::ImageLayer;
use crate::tile_fetcher::HiPSLocalFiles;
use crate::Abort;
use crate::{
camera::CameraViewPort,
downloader::Downloader,
@@ -25,11 +22,9 @@ use crate::{
time::DeltaTime,
};
use al_api::moc::MOCOptions;
use al_core::image::bitmap::Bitmap;
use al_core::image::fits::FitsImage;
use al_core::image::ImageType;
use fitsrs::WCS;
use moclib::qty::{Frequency, MocQty};
use std::io::Cursor;
use wasm_bindgen::prelude::*;
@@ -51,6 +46,8 @@ use web_sys::{HtmlElement, WebGl2RenderingContext};
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashSet;
use crate::renderable::final_pass::RenderPass;
use al_core::FrameBufferObject;
@@ -108,8 +105,6 @@ pub struct App {
//img_send: async_channel::Sender<ImageLayer>,
img_recv: async_channel::Receiver<ImageLayer>,
ack_img_send: async_channel::Sender<ImageParams>,
browser_features_support: BrowserFeaturesSupport,
//ack_img_recv: async_channel::Receiver<ImageParams>,
// callbacks
//callback_position_changed: js_sys::Function,
@@ -123,8 +118,8 @@ pub const BLENDING_ANIM_DURATION: DeltaTime = DeltaTime::from_millis(200.0); //
use crate::time::Time;
use cgmath::InnerSpace;
use crate::downloader::query::{self, CellDesc};
use crate::downloader::request::RequestType;
use crate::downloader::query;
use crate::downloader::request;
use al_api::resources::Resources;
impl App {
@@ -140,6 +135,7 @@ impl App {
//let exec = Rc::new(RefCell::new(TaskExecutor::new()));
let projection = ProjectionType::Sin(mapproj::zenithal::sin::Sin);
gl.enable(WebGl2RenderingContext::BLEND);
// TODO: https://caniuse.com/?search=scissor is not supported for safari <= 14.1
// When it will be supported nearly everywhere, we will need to uncomment this line to
@@ -206,8 +202,6 @@ impl App {
let dragging = false;
let time_mouse_high_vel = Time::now();
let browser_features_support = BrowserFeaturesSupport::new();
Ok(App {
gl,
//ui,
@@ -259,8 +253,7 @@ impl App {
//img_send,
img_recv,
ack_img_send,
browser_features_support, //ack_img_recv,
//ack_img_recv,
})
}
@@ -279,15 +272,48 @@ impl App {
}
}
// no Allsky generated for HiPS3D
HiPS::D3(_) => (),
HiPS::D3(h) => (),
}
}
hips.look_for_new_tiles(
&mut self.tile_fetcher,
&self.camera,
&self.browser_features_support,
);
let cfg = hips.get_config();
let min_tile_depth = cfg.get_min_depth_tile();
let mut ancestors = HashSet::new();
if let Some(tiles) = hips.look_for_new_tiles(&self.camera) {
for tile_cell in tiles {
self.tile_fetcher.append(hips.get_tile_query(&tile_cell));
// check if we are starting aladin lite or not.
// If so we want to retrieve only the tiles in the view and access them
// directly i.e. without blending them with less precised tiles
if self.tile_fetcher.get_num_tile_fetched() > 0
&& tile_cell.depth() >= min_tile_depth + 3
{
let ancestor_tile_cell = tile_cell.ancestor(3);
ancestors.insert(ancestor_tile_cell);
}
}
}
// Request for ancestor
match hips {
HiPS::D2(hips) => {
for ancestor in ancestors {
if !hips.update_priority_tile(&ancestor) {
self.tile_fetcher.append(hips.get_tile_query(&ancestor));
}
}
}
HiPS::D3(hips) => {
let freq = hips.get_freq();
for ancestor in ancestors {
if !hips.contains_tile(&ancestor, freq) {
self.tile_fetcher.append(hips.get_tile_query(&ancestor));
}
}
}
}
}
Ok(())
@@ -368,9 +394,10 @@ impl App {
}*/
}
use crate::downloader::request::Resource;
use al_api::cell::HEALPixCellProjeted;
use crate::healpix::cell::{HEALPixCell, HEALPixFreqCell};
use crate::healpix::cell::HEALPixCell;
use al_api::color::ColorRGB;
@@ -493,10 +520,6 @@ impl App {
}
pub(crate) fn update(&mut self, dt: DeltaTime) -> Result<bool, JsValue> {
// a timer stopping the frame if it takes too long
// useful for garanting a framerate
let rendering_timer = Time::now();
if let Some(inertia) = self.inertia.as_mut() {
inertia.apply(&mut self.camera, &self.projection, dt);
// Always request for new tiles while moving
@@ -570,47 +593,28 @@ impl App {
let rscs_received = self.downloader.borrow_mut().get_received_resources();
let mut tile_copied = false;
const MAX_FRAME_TIME: DeltaTime = DeltaTime::from_millis(1000.0 / 25.0);
//let mut tile_copied = false;
for rsc in rscs_received {
if Time::now() - rendering_timer >= MAX_FRAME_TIME {
self.downloader.borrow_mut().delay(rsc);
continue;
}
match rsc {
RequestType::Tile(tile) => {
/*if self.camera.has_moved() {
self.downloader
.borrow_mut()
.delay(RequestType::Tile(tile));
continue;
}*/
if let Some(hips) = self.layers.get_mut_hips_from_cdid(&tile.hips_cdid) {
let cfg = hips.get_config();
Resource::Tile(tile) => {
//if !_has_camera_zoomed {
if let Some(hips) = self.layers.get_mut_hips_from_cdid(tile.get_hips_cdid()) {
let cfg = hips.get_config_mut();
if cfg.get_format() == tile.format {
let fov_coverage = self.camera.get_cov(cfg.get_frame());
let hpx_cell = tile.cell.get_hpx();
let included_in_coverage = fov_coverage.intersects_cell(hpx_cell);
let included_in_coverage = fov_coverage.intersects_cell(tile.cell());
//let is_tile_root = tile.cell().depth() == delta_depth;
//let _depth = tile.cell().depth();
// do not perform tex_sub costly GPU calls while the camera is zooming
if hpx_cell.is_root() || included_in_coverage {
let image = tile.request.get_data().clone();
// 1. For FITS tiles, parse the bscale/bzero and optional blank
// FIXME. We should consider these constants as per tiles and not for
// whole HiPS they belong to.
if tile.cell().is_root() || included_in_coverage {
//if let Some(image) = image.as_ref() {
if let Some(ImageType::FitsRawBytes {
raw_bytes: raw_bytes_buf,
..
}) = &*image.borrow()
}) = &*tile.image.borrow()
{
// check if the metadata has not been set
if hips.get_fits_params().is_none() {
@@ -626,267 +630,44 @@ impl App {
}
};
// 2. Add the tile to its HiPS
let image = tile.image.clone();
if let Some(img) = &*image.borrow() {
// For PNG/JPEG cubic tiles, all the slices are in the lonely image
match (&tile.cell, hips) {
(CellDesc::HiPS2D { cell, .. }, HiPS::D2(hips)) => {
hips.push_tile(cell, img, tile.request.time_request)?
}
(
CellDesc::HiPSCube { cell, channel, .. },
HiPS::D3(hips),
) => {
// We build an artificial cube
let f_hash = (*channel / 32) as u64;
let slice_idx = (*channel % 32) as u16;
/*if tile_copied {
self.downloader
.borrow_mut()
.delay(Resource::Tile(tile));
continue;
}*/
let cell = HEALPixFreqCell::new(
*cell,
f_hash,
Frequency::<u64>::MAX_DEPTH,
);
hips.push_tile_slice(
&cell,
img,
tile.request.time_request,
slice_idx,
)?
}
(
CellDesc::HiPS3D {
cell,
tile_size,
tile_depth,
},
HiPS::D3(hips),
) => {
// As the decoding and copying to the GPU of cubic tile is more costly
// (not that much but there is more because they are smaller)
// then we delay their treatment through the frames
if tile_copied {
self.downloader
.borrow_mut()
.delay(RequestType::Tile(tile));
continue;
}
tile_copied = true;
// TODO PNG/JPG case to handle here
match img {
ImageType::ImageRgba8u {
image: Bitmap { image, .. },
} => {
let document = web_sys::window()
.unwrap_abort()
.document()
.unwrap_abort();
let canvas = document
.create_element("canvas")?
.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_width(image.width());
canvas.set_height(image.height());
let context = canvas
.get_context("2d")?
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
// Get the data once for all for the whole image
// This takes time so better do it once and not repeatly
context.draw_image_with_image_bitmap(
image, 0.0, 0.0,
)?;
// Cut the png in several tile images. See page 3 of
// https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf
let tile_depth = *tile_depth;
let num_cols =
(tile_depth as f32).sqrt().floor() as u32;
let num_rows = ((tile_depth as f32)
/ (num_cols as f32))
.ceil()
as u32;
let tile_size = *tile_size;
let bytes = context
.get_image_data(
0_f64,
0_f64,
(num_cols * tile_size) as f64,
(num_rows * tile_size) as f64,
)?
.data()
.0;
let mut decoded_bytes = vec![
0_u8;
(tile_size * tile_size * tile_depth * 2)
as usize
];
let mut k = 0;
let mut num_tiles_cropped = 0;
for y in 0..num_rows {
let sy = y * tile_size;
for x in 0..num_cols {
let sx = x * tile_size;
for i in sy..(sy + tile_size) {
for j in sx..(sx + tile_size) {
let id_byte = (j + i
* num_cols
* tile_size)
* 4;
decoded_bytes[k] =
bytes[id_byte as usize];
decoded_bytes[k + 1] =
bytes[id_byte as usize + 3];
k += 2;
}
}
num_tiles_cropped += 1;
if num_tiles_cropped == tile_depth {
break;
}
}
if num_tiles_cropped == tile_depth {
break;
}
}
hips.push_tile_from_png(
cell,
decoded_bytes.into_boxed_slice(),
(tile_size, tile_size, tile_depth),
tile.request.time_request,
)?;
}
ImageType::ImageRgb8u {
image: Bitmap { image, .. },
} => {
let document = web_sys::window()
.unwrap_abort()
.document()
.unwrap_abort();
let canvas = document
.create_element("canvas")?
.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_width(image.width());
canvas.set_height(image.height());
let context = canvas
.get_context("2d")?
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
// Get the data once for all for the whole image
// This takes time so better do it once and not repeatly
context.draw_image_with_image_bitmap(
image, 0.0, 0.0,
)?;
// Cut the png in several tile images. See page 3 of
// https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf
let tile_depth = *tile_depth;
let num_cols =
(tile_depth as f32).sqrt().floor() as u32;
let num_rows = ((tile_depth as f32)
/ (num_cols as f32))
.ceil()
as u32;
let tile_size = *tile_size;
let bytes = context
.get_image_data(
0_f64,
0_f64,
(num_cols * tile_size) as f64,
(num_rows * tile_size) as f64,
)?
.data()
.0;
let mut decoded_bytes = vec![
0_u8;
(tile_size * tile_size * tile_depth)
as usize
];
let mut k = 0;
let mut num_tiles_cropped = 0;
for y in 0..num_rows {
let sy = y * tile_size;
for x in 0..num_cols {
let sx = x * tile_size;
for i in sy..(sy + tile_size) {
for j in sx..(sx + tile_size) {
let id_byte = (j + i
* num_cols
* tile_size)
* 4;
decoded_bytes[k] =
bytes[id_byte as usize];
k += 1;
}
}
num_tiles_cropped += 1;
if num_tiles_cropped == tile_depth {
break;
}
}
if num_tiles_cropped == tile_depth {
break;
}
}
hips.push_tile_from_jpeg(
cell,
decoded_bytes.into_boxed_slice(),
(tile_size, tile_size, tile_depth),
tile.request.time_request,
)?;
}
ImageType::FitsRawBytes { raw_bytes, size } => hips
.push_tile_from_fits(
cell,
raw_bytes.clone(),
*size,
tile.request.time_request,
)?,
_ => unreachable!(),
}
}
_ => unreachable!(),
}
self.request_redraw = true;
//tile_copied = true;
match hips {
HiPS::D2(hips) => {
hips.add_tile(&tile.cell, img, tile.time_req)?
}
HiPS::D3(hips) => hips.add_tile(
&tile.cell,
img,
tile.time_req,
tile.channel.unwrap() as u16,
)?,
}
self.time_start_blending = Time::now();
};
}
}
}
}
RequestType::Allsky(allsky) => {
if let Some(HiPS::D2(hips)) =
self.layers.get_mut_hips_from_cdid(&allsky.hips_cdid)
{
Resource::Allsky(allsky) => {
let hips_cdid = allsky.get_hips_cdid();
if let Some(hips) = self.layers.get_mut_hips_from_cdid(hips_cdid) {
let is_missing = allsky.missing();
if is_missing {
// The allsky image is missing so we donwload all the tiles contained into
// the 0's cell
for base_hpx_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
let query = query::Tile::new(
base_hpx_cell,
hips.get_config(),
&self.browser_features_support,
);
let query = hips.get_tile_query(base_hpx_cell);
self.tile_fetcher.append_base_tile(query);
}
} else {
@@ -898,12 +679,12 @@ impl App {
}
}
}
RequestType::Moc(moc) => {
let moc_hips_cdid = moc.hips_cdid;
Resource::Moc(fetched_moc) => {
let moc_hips_cdid = fetched_moc.get_hips_cdid();
//let url = &moc_url[..moc_url.find("/Moc.fits").unwrap_abort()];
if let Some(hips) = self.layers.get_mut_hips_from_cdid(&moc_hips_cdid) {
let MOCRequest { request, .. } = moc;
if let Some(moc) = &*request.get_data().borrow() {
if let Some(hips) = self.layers.get_mut_hips_from_cdid(moc_hips_cdid) {
let request::moc::FetchedMoc { moc, .. } = fetched_moc;
if let Some(moc) = &*moc.borrow() {
match (hips, moc) {
(HiPS::D2(hips), Moc::Space(moc)) => {
hips.set_moc(moc.clone());
@@ -1056,7 +837,6 @@ impl App {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
@@ -1072,12 +852,12 @@ impl App {
);*/
moc.draw(camera, projection, shaders)?;
/*gl.blend_func_separate(
gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
);
grid.draw(camera, projection, shaders)?;
// Ok(())
// },
@@ -1270,11 +1050,7 @@ impl App {
self.layers.get_layer_cfg(layer)
}
pub(crate) fn set_hips_frequency(
&mut self,
layer: &str,
frequency: f32,
) -> Result<(), JsValue> {
pub(crate) fn set_hips_slice_number(&mut self, layer: &str, slice: u32) -> Result<(), JsValue> {
let hips = self
.layers
.get_mut_hips_from_layer(layer)
@@ -1285,49 +1061,13 @@ impl App {
match hips {
HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")),
HiPS::D3(hips) => {
hips.set_freq(Freq(frequency as f64));
hips.set_freq(Freq(slice as f64));
Ok(())
}
}
}
pub(crate) fn get_hips_frequency(&mut self, layer: &str) -> Result<f32, JsValue> {
let hips = self
.layers
.get_mut_hips_from_layer(layer)
.ok_or_else(|| JsValue::from_str("Layer not found"))?;
match hips {
HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")),
HiPS::D3(hips) => Ok(hips.get_freq().0 as f32),
}
}
pub(crate) fn get_freq_from_hash(&mut self, layer: &str, hash: u64) -> Result<f64, JsValue> {
let hips = self
.layers
.get_mut_hips_from_layer(layer)
.ok_or_else(|| JsValue::from_str("Layer not found"))?;
match hips {
HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")),
HiPS::D3(hips) => Ok(hips.get_freq_from_hash(hash).0),
}
}
pub(crate) fn get_freq_hash(&mut self, layer: &str, freq: f64) -> Result<u64, JsValue> {
let hips = self
.layers
.get_mut_hips_from_layer(layer)
.ok_or_else(|| JsValue::from_str("Layer not found"))?;
match hips {
HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")),
HiPS::D3(hips) => Ok(hips.get_freq_hash(Freq(freq))),
}
}
pub(crate) fn set_image_hips_color_cfg(
&mut self,
layer: String,

View File

@@ -1,25 +0,0 @@
use js_sys::Reflect;
use wasm_bindgen::JsValue;
use web_sys::window;
pub struct BrowserFeaturesSupport {
pub create_image_bitmap: bool,
}
impl Default for BrowserFeaturesSupport {
fn default() -> Self {
Self::new()
}
}
impl BrowserFeaturesSupport {
pub fn new() -> Self {
let window = window().expect("no global `window` exists");
let create_image_bitmap =
Reflect::has(&window, &JsValue::from_str("createImageBitmap")).unwrap_or(false);
Self {
create_image_bitmap,
}
}
}

View File

@@ -54,8 +54,8 @@ fn linspace(a: f64, b: f64, num: usize) -> Vec<f64> {
res
}
const NUM_VERTICES_WIDTH: usize = 4;
const NUM_VERTICES_HEIGHT: usize = 4;
const NUM_VERTICES_WIDTH: usize = 3;
const NUM_VERTICES_HEIGHT: usize = 3;
const NUM_VERTICES: usize = 4 + 2 * NUM_VERTICES_WIDTH + 2 * NUM_VERTICES_HEIGHT;
// This struct belongs to the CameraViewPort
pub struct FieldOfView {

View File

@@ -10,13 +10,13 @@ pub struct Downloader {
requests: Vec<RequestType>,
queried_list: HashSet<QueryId>,
cache: Cache<QueryId, RequestType>,
cache: Cache<QueryId, Resource>,
}
use crate::fifo_cache::Cache;
use query::Query;
use request::RequestType;
use request::{RequestType, Resource};
impl Default for Downloader {
fn default() -> Self {
@@ -62,23 +62,26 @@ impl Downloader {
}
}
pub fn get_received_resources(&mut self) -> Vec<RequestType> {
pub fn get_received_resources(&mut self) -> Vec<Resource> {
let mut rscs = vec![];
let mut not_finished_requests = vec![];
let mut finished_query_list = vec![];
self.requests = self
.requests
.drain(..)
.filter(|request| {
// If the request resolves into a resource
if let Some(rsc) = request.into() {
rscs.push(rsc);
finished_query_list.push(request.id().clone());
while let Some(request) = self.requests.pop() {
if request.is_resolved() {
finished_query_list.push(request.id().clone());
rscs.push(request);
// The request is not resolved, we keep it
} else {
not_finished_requests.push(request);
}
}
self.requests = not_finished_requests;
false
// The request is not resolved, we keep it
} else {
true
}
})
.collect();
for query_id in finished_query_list.into_iter() {
self.queried_list.remove(&query_id);
@@ -95,10 +98,16 @@ impl Downloader {
self.queried_list.contains(id)
}
pub fn delay(&mut self, r: RequestType) {
pub fn delay(&mut self, r: Resource) {
match r {
RequestType::Tile(tile) => {
self.cache.insert(tile.id.clone(), RequestType::Tile(tile));
Resource::Tile(tile) => {
let k = format!(
"{:?}{:?}/{:?}",
tile.get_hips_cdid(),
tile.cell.depth(),
tile.cell.idx()
);
self.cache.insert(k, Resource::Tile(tile));
}
_ => unimplemented!(),
}

View File

@@ -6,87 +6,38 @@ pub trait Query: Sized {
fn id(&self) -> &QueryId;
}
pub type QueryId = String;
use crate::browser_support::BrowserFeaturesSupport;
use crate::healpix::cell::HEALPixFreqCell;
use al_api::hips::DataproductType;
use al_core::image::format::ImageFormatType;
/// Description of a cell to query
#[derive(Clone, PartialEq, Eq)]
pub enum CellDesc {
HiPS2D {
// A description of the tile in space
cell: HEALPixCell,
// Size of the tile requested
tile_size: u32,
},
HiPS3D {
// A description of the tile in space and frequency
cell: HEALPixFreqCell,
// Size of the tile requested
tile_size: u32,
// Depth of the cubic tile
tile_depth: u32,
},
HiPSCube {
// A description of the tile in space
cell: HEALPixCell,
// size of the tile requested
tile_size: u32,
// The channel number to query
channel: u32,
},
}
impl CellDesc {
/*fn get_size(&self) -> (u32, u32, u32) {
match self {
Self::HiPS2D { tile_size, .. } => (*tile_size, *tile_size, 1),
Self::HiPSCube { tile_size, .. } => (*tile_size, *tile_size, 1),
Self::HiPS3D {
tile_size,
tile_depth,
..
} => (*tile_size, *tile_size, *tile_depth),
}
}*/
pub fn get_hpx(&self) -> &HEALPixCell {
match self {
Self::HiPS2D { cell, .. } => cell,
Self::HiPS3D { cell, .. } => &cell.hpx,
Self::HiPSCube { cell, .. } => cell,
}
}
}
#[derive(Eq, PartialEq, Clone)]
pub struct Tile {
pub cell: CellDesc,
pub cell: HEALPixCell,
pub format: ImageFormatType,
// The root url of the HiPS
pub hips_cdid: CreatorDid,
// The total url of the query
pub url: Url,
pub size: u32, // size of the tile requested
pub credentials: RequestCredentials,
pub mode: RequestMode,
pub id: QueryId,
pub create_bitmap_support: bool,
pub channel: Option<u32>,
}
/*pub enum {
}*/
use crate::healpix::cell::HEALPixCell;
use crate::renderable::hips::config::HiPSConfig;
use crate::renderable::CreatorDid;
use crate::tile_fetcher::HiPSLocalFiles;
use web_sys::{RequestCredentials, RequestMode};
impl Tile {
pub fn new(
cell: &HEALPixCell,
cfg: &HiPSConfig,
browser_support: &BrowserFeaturesSupport,
) -> Self {
pub fn new(cell: &HEALPixCell, channel: Option<u32>, cfg: &HiPSConfig) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
@@ -99,108 +50,38 @@ impl Tile {
let dir_idx = (idx / 10000) * 10000;
let url = format!("{hips_url}/Norder{depth}/Dir{dir_idx}/Npix{idx}.{ext}");
let mut url = format!("{hips_url}/Norder{depth}/Dir{dir_idx}/Npix{idx}");
let id = format!("{}_{}_{}_{}", hips_cdid, depth, idx, ext);
let tile_size = cfg.get_tile_size() as u32;
Tile {
hips_cdid: hips_cdid.to_string(),
url,
cell: CellDesc::HiPS2D {
cell: *cell,
tile_size,
},
format,
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
// handle cube case
if let Some(channel) = channel {
if channel > 0 {
url.push_str(&format!("_{channel:?}"));
}
}
}
pub fn new_with_channel(
cell: &HEALPixCell,
channel: u32,
cfg: &HiPSConfig,
browser_support: &BrowserFeaturesSupport,
) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
let credentials = cfg.get_request_credentials();
let mode = cfg.get_request_mode();
// add the tile format
url.push_str(&format!(".{ext}"));
let ext = format.get_ext_file();
let id = format!(
"{}{}{}{}{}",
hips_cdid,
depth,
idx,
channel.unwrap_or(0),
ext
);
let HEALPixCell(depth, idx) = *cell;
let dir_idx = (idx / 10000) * 10000;
let url = format!("{hips_url}/Norder{depth}/Dir{dir_idx}/Npix{idx}_{channel:?}.{ext}");
let id = format!("{}_{}_{}_{}_{}", hips_cdid, depth, idx, channel, ext);
let tile_size = cfg.get_tile_size() as u32;
let size = cfg.get_tile_size() as u32;
Tile {
hips_cdid: hips_cdid.to_string(),
url,
cell: CellDesc::HiPSCube {
cell: *cell,
tile_size,
channel,
},
cell: *cell,
format,
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
}
}
pub fn new_cubic(
hpx_f_cell: &HEALPixFreqCell,
cfg: &HiPSConfig,
browser_support: &BrowserFeaturesSupport,
) -> Self {
let hips_cdid = cfg.get_creator_did();
let hips_url = cfg.get_root_url();
let format = cfg.get_format();
let credentials = cfg.get_request_credentials();
let mode = cfg.get_request_mode();
let ext = format.get_ext_file();
// f hash at order_f
let HEALPixFreqCell {
hpx: HEALPixCell(k, n),
f_hash: m,
f_depth: l,
} = *hpx_f_cell;
let d = (n / 10000) * 10000;
let e = (m / 10) * 10;
let url = format!("{hips_url}/Norder{k}_{l}/Dir{d}_{e}/Npix{n}_{m}.{ext}");
let id = format!("{hips_cdid}_{k}_{l}_{n}_{m}_{ext}");
let tile_size = cfg.get_tile_size() as u32;
let tile_depth = cfg.tile_depth.unwrap_or(1) as u32;
Tile {
hips_cdid: hips_cdid.to_string(),
url,
cell: CellDesc::HiPS3D {
cell: hpx_f_cell.clone(),
tile_size,
tile_depth,
},
format,
credentials,
mode,
id,
create_bitmap_support: browser_support.create_image_bitmap,
channel,
size,
}
}
}

View File

@@ -14,13 +14,7 @@ pub struct AllskyRequest {
pub id: QueryId,
pub channel: Option<u32>,
pub request: Request<Vec<ImageType>>,
}
impl AllskyRequest {
pub fn missing(&self) -> bool {
self.request.data.borrow().is_none()
}
request: Request<Vec<ImageType>>,
}
impl From<AllskyRequest> for RequestType {
@@ -63,7 +57,7 @@ async fn query_allsky(
let raw_bytes = image_data.data();
Ok(ImageBuffer::from_raw_bytes(raw_bytes.0, w, h))
Ok(ImageBuffer::from_raw_bytes(raw_bytes.0, w as i32, h as i32))
}
impl From<query::Allsky> for AllskyRequest {
@@ -95,18 +89,12 @@ impl From<query::Allsky> for AllskyRequest {
.map(|image| {
let ImageBuffer { data, size } = image;
let data = data
.iter()
.into_iter()
.enumerate()
.filter(|&(i, _)| i % 4 != 3)
.map(|(_, v)| *v)
.collect::<Vec<_>>();
let image = ImageBuffer::new(
data.into_boxed_slice(),
size.0,
size.1,
size.2,
);
.map(|(_, v)| v)
.collect();
let image = ImageBuffer::new(data, size.x, size.y);
ImageType::RawRgb8u { image }
})
@@ -232,11 +220,8 @@ fn handle_allsky_file<F: TextureFormat>(
let mut src_idx = 0;
let tiles = (0..12).map(move |_| {
let mut base_tile = ImageBuffer::<F>::allocate(
&F::P::BLACK,
allsky_tile_size as u32,
allsky_tile_size as u32,
);
let mut base_tile =
ImageBuffer::<F>::allocate(&F::P::BLACK, allsky_tile_size, allsky_tile_size);
for idx_tile in 0..64 {
let (x, y) = crate::utils::unmortonize(idx_tile as u64);
let dx = x * (d3_tile_allsky_size as u32);
@@ -283,14 +268,8 @@ fn handle_allsky_fits<F: TextureFormat>(
.rev()
.flatten()
.copied()
.collect::<Vec<_>>()
.into_boxed_slice();
let image = ImageBuffer::<F>::new(
reversed_rows_data,
width_allsky_px as u32,
height_allsky_px as u32,
1,
);
.collect::<Vec<_>>();
let image = ImageBuffer::<F>::new(reversed_rows_data, width_allsky_px, height_allsky_px);
let allsky_tiles_iter =
handle_allsky_file::<F>(image, allsky_tile_size, tile_size)?.map(move |image| {
@@ -307,12 +286,7 @@ fn handle_allsky_fits<F: TextureFormat>(
.cloned()
.collect();
ImageBuffer::<F>::new(
new_image_data,
allsky_tile_size as u32,
allsky_tile_size as u32,
1,
)
ImageBuffer::<F>::new(new_image_data, allsky_tile_size, allsky_tile_size)
});
Ok(allsky_tiles_iter)
@@ -320,4 +294,59 @@ fn handle_allsky_fits<F: TextureFormat>(
use al_core::texture::format::RGBA8U;
use crate::time::Time;
use std::cell::RefCell;
use std::rc::Rc;
pub struct Allsky {
pub image: Rc<RefCell<Option<Vec<ImageType>>>>,
pub time_req: Time,
//pub depth_tile: u8,
pub hips_cdid: CreatorDid,
url: Url,
pub channel: Option<u32>,
}
use crate::Abort;
impl Allsky {
pub fn missing(&self) -> bool {
self.image.borrow().is_none()
}
pub fn get_hips_cdid(&self) -> &CreatorDid {
&self.hips_cdid
}
pub fn get_url(&self) -> &Url {
&self.url
}
}
impl<'a> From<&'a AllskyRequest> for Option<Allsky> {
fn from(request: &'a AllskyRequest) -> Self {
let AllskyRequest {
request,
hips_cdid,
//depth_tile,
url,
channel,
..
} = request;
if request.is_resolved() {
let Request::<Vec<ImageType>> {
time_request, data, ..
} = request;
Some(Allsky {
time_req: *time_request,
// This is a clone on a Arc, it is supposed to be fast
image: data.clone(),
hips_cdid: hips_cdid.clone(),
url: url.clone(),
//depth_tile: *depth_tile,
channel: *channel,
})
} else {
None
}
}
}

View File

@@ -6,12 +6,14 @@ use super::{Request, RequestType};
use crate::healpix::moc::Moc;
use crate::healpix::moc::{FreqSpaceMoc, SpaceMoc};
use al_api::hips::DataproductType;
use moclib::deser::fits::MocType;
use moclib::qty::Hpx;
pub struct MOCRequest {
//pub id: QueryId,
pub hips_cdid: CreatorDid,
pub params: MOCOptions,
pub request: Request<Moc>,
request: Request<Moc>,
}
impl From<MOCRequest> for RequestType {
@@ -19,6 +21,7 @@ impl From<MOCRequest> for RequestType {
RequestType::Moc(request)
}
}
use super::Url;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
@@ -27,6 +30,8 @@ use web_sys::{RequestInit, Response};
use crate::Abort;
use al_api::moc::MOCOptions;
use std::io::Cursor;
use wasm_bindgen::JsValue;
impl From<query::Moc> for MOCRequest {
// Create a tile request associated to a HiPS
fn from(query: query::Moc) -> Self {
@@ -63,10 +68,6 @@ impl From<query::Moc> for MOCRequest {
DataproductType::SpectralCube => {
Moc::FreqSpace(FreqSpaceMoc::from_fits_raw_bytes(&bytes)?)
}
DataproductType::Cube => {
let moc = SpaceMoc::from_fits_raw_bytes(&bytes)?;
Moc::FreqSpace(FreqSpaceMoc::from_space_moc(moc))
}
_ => Moc::Space(SpaceMoc::from_fits_raw_bytes(&bytes)?),
})
});
@@ -80,3 +81,39 @@ impl From<query::Moc> for MOCRequest {
}
}
}
use std::cell::RefCell;
use std::rc::Rc;
pub struct FetchedMoc {
pub moc: Rc<RefCell<Option<Moc>>>,
pub params: MOCOptions,
pub hips_cdid: Url,
}
impl FetchedMoc {
pub fn get_hips_cdid(&self) -> &Url {
&self.hips_cdid
}
}
impl<'a> From<&'a MOCRequest> for Option<FetchedMoc> {
fn from(request: &'a MOCRequest) -> Self {
let MOCRequest {
request,
hips_cdid,
params,
..
} = request;
if request.is_resolved() {
let Request::<Moc> { data, .. } = request;
Some(FetchedMoc {
// This is a clone on a Arc, it is supposed to be fast
moc: data.clone(),
hips_cdid: hips_cdid.clone(),
params: params.clone(),
})
} else {
None
}
}
}

View File

@@ -11,8 +11,8 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub type Url = String;
pub struct Request<R> {
pub data: Rc<RefCell<Option<R>>>,
pub time_request: Time,
data: Rc<RefCell<Option<R>>>,
time_request: Time,
// Flag telling if the tile has been copied so that
// the HtmlImageElement can be reused to download another tile
//ready: bool,
@@ -75,10 +75,6 @@ where
pub fn resolve_status(&self) -> ResolvedStatus {
self.resolved.get()
}
pub fn get_data(&self) -> Rc<RefCell<Option<R>>> {
self.data.clone()
}
}
use allsky::AllskyRequest;
@@ -87,7 +83,7 @@ use tile::TileRequest;
pub enum RequestType {
Tile(TileRequest),
Allsky(AllskyRequest),
Moc(MOCRequest),
Moc(MOCRequest), //..
}
use crate::downloader::QueryId;
@@ -99,30 +95,30 @@ impl RequestType {
RequestType::Moc(request) => &request.hips_cdid,
}
}
pub fn is_resolved(&self) -> bool {
match self {
RequestType::Tile(request) => request.request.is_resolved(),
RequestType::Allsky(request) => request.request.is_resolved(),
RequestType::Moc(request) => request.request.is_resolved(),
}
}
}
/*
impl From<RequestType> for Option<Resource> {
fn from(request: RequestType) -> Self {
impl<'a> From<&'a RequestType> for Option<Resource> {
fn from(request: &'a RequestType) -> Self {
match request {
RequestType::Tile(request) => Option::<Tile>::from(request).map(Resource::Tile),
RequestType::Allsky(request) => Option::<Allsky>::from(request).map(Resource::Allsky),
RequestType::Moc(request) => Option::<FetchedMoc>::from(request).map(Resource::Moc),
}
}
}*/
}
use crate::Abort;
use allsky::Allsky;
use tile::Tile;
pub enum Resource {
Tile(Tile),
Allsky(Allsky),
Moc(FetchedMoc),
}
use web_sys::RequestCredentials;
use self::moc::FetchedMoc;
async fn query_html_image(
url: &str,
credentials: RequestCredentials,
@@ -151,39 +147,3 @@ async fn query_html_image(
Ok(image)
}
use wasm_bindgen::JsCast;
use web_sys::RequestInit;
use web_sys::RequestMode;
use web_sys::Response;
async fn query_bitmap_from_blob(
url: &str,
mode: RequestMode,
credentials: RequestCredentials,
) -> Result<web_sys::ImageBitmap, JsValue> {
let window = web_sys::window().unwrap_abort();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(mode);
opts.credentials(credentials);
let request = web_sys::Request::new_with_str_and_init(url, &opts).unwrap_abort();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
// `resp_value` is a `Response` object.
debug_assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into()?;
if resp.ok() {
let blob = JsFuture::from(resp.blob()?)
.await?
.dyn_into::<web_sys::Blob>()?;
let image_bitmap = JsFuture::from(window.create_image_bitmap_with_blob(&blob)?).await?;
Ok(image_bitmap.into())
} else {
Err(JsValue::from_str(
"Response status code not between 200-299.",
))
}
}

View File

@@ -1,24 +1,24 @@
use crate::healpix::cell::HEALPixCell;
use crate::renderable::CreatorDid;
use al_core::image::format::ImageFormatType;
use al_core::texture::format::PixelType;
use al_core::texture::format::{PixelType, RGB8U, RGBA8U};
use crate::downloader::query;
use al_core::image::ImageType;
use super::super::query::CellDesc;
use super::Url;
use super::{Request, RequestType};
use crate::downloader::request::query_html_image;
use crate::downloader::QueryId;
pub struct TileRequest {
pub request: Request<ImageType>,
request: Request<ImageType>,
pub id: QueryId,
pub cell: CellDesc,
pub hips_cdid: CreatorDid,
pub url: Url,
pub format: ImageFormatType,
cell: HEALPixCell,
hips_cdid: CreatorDid,
url: Url,
format: ImageFormatType,
channel: Option<u32>,
}
impl From<TileRequest> for RequestType {
@@ -27,14 +27,11 @@ impl From<TileRequest> for RequestType {
}
}
use crate::downloader::request::query_bitmap_from_blob;
use al_core::image::bitmap::Bitmap;
use al_core::image::html::HTMLImage;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::{RequestInit, Response};
impl From<query::Tile> for TileRequest {
// Create a tile request associated to a HiPS
fn from(query: query::Tile) -> Self {
@@ -46,62 +43,33 @@ impl From<query::Tile> for TileRequest {
credentials,
mode,
id,
create_bitmap_support,
channel,
size,
} = query;
let url_clone = url.clone();
let pixel_format = format.get_pixel_format();
let size = match cell {
CellDesc::HiPS2D { tile_size, .. } | CellDesc::HiPSCube { tile_size, .. } => {
(tile_size, tile_size, 1)
}
CellDesc::HiPS3D {
tile_size,
tile_depth,
..
} => (tile_size, tile_size, tile_depth),
};
let window = web_sys::window().unwrap_abort();
let request = match pixel_format {
PixelType::RGB8U => Request::new(async move {
if create_bitmap_support {
// optimized download of tile for GPU (using Blob + Bitmap) without creating any DOM structure
let image_bitmap =
query_bitmap_from_blob(&url_clone, mode, credentials).await?;
Ok(ImageType::ImageRgb8u {
image: Bitmap::new(image_bitmap),
})
} else {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgb8u {
image: HTMLImage::new(image),
})
}
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgb8u {
image: HTMLImage::<RGB8U>::new(image),
})
}),
PixelType::RGBA8U => Request::new(async move {
if create_bitmap_support {
// optimized download of tile for GPU (using Blob + Bitmap) without creating any DOM structure
let image_bitmap =
query_bitmap_from_blob(&url_clone, mode, credentials).await?;
Ok(ImageType::ImageRgba8u {
image: Bitmap::new(image_bitmap),
})
} else {
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgba8u {
image: HTMLImage::new(image),
})
}
// HTMLImageElement
let image = query_html_image(&url_clone, credentials).await?;
// The image has been resolved
Ok(ImageType::HTMLImageRgba8u {
image: HTMLImage::<RGBA8U>::new(image),
})
}),
PixelType::R32F | PixelType::R32I | PixelType::R16I | PixelType::R8U => {
Request::new(async move {
let window = web_sys::window().unwrap_abort();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(mode);
@@ -125,7 +93,10 @@ impl From<query::Tile> for TileRequest {
let array_buffer = JsFuture::from(resp.array_buffer()?).await?;
let raw_bytes = js_sys::Uint8Array::new(&array_buffer);
Ok(ImageType::FitsRawBytes { raw_bytes, size })
Ok(ImageType::FitsRawBytes {
raw_bytes,
size: (size, size),
})
} else {
Err(JsValue::from_str(
"Response status code not between 200-299.",
@@ -142,8 +113,74 @@ impl From<query::Tile> for TileRequest {
hips_cdid,
url,
request,
channel,
}
}
}
use crate::time::Time;
use std::cell::RefCell;
use std::rc::Rc;
pub struct Tile {
pub image: Rc<RefCell<Option<ImageType>>>,
pub time_req: Time,
pub cell: HEALPixCell,
pub format: ImageFormatType,
pub channel: Option<u32>,
hips_cdid: CreatorDid,
url: Url,
}
use crate::Abort;
impl Tile {
#[inline(always)]
pub fn missing(&self) -> bool {
self.image.borrow().is_none()
}
#[inline(always)]
pub fn get_hips_cdid(&self) -> &CreatorDid {
&self.hips_cdid
}
#[inline(always)]
pub fn get_url(&self) -> &Url {
&self.url
}
#[inline(always)]
pub fn cell(&self) -> &HEALPixCell {
&self.cell
}
}
impl<'a> From<&'a TileRequest> for Option<Tile> {
fn from(request: &'a TileRequest) -> Self {
let TileRequest {
cell,
request,
hips_cdid,
url,
format,
channel,
..
} = request;
if request.is_resolved() {
let Request::<ImageType> {
time_request, data, ..
} = request;
Some(Tile {
cell: *cell,
time_req: *time_request,
// This is a clone on a Arc, it is supposed to be fast
image: data.clone(),
hips_cdid: hips_cdid.clone(),
url: url.clone(),
format: *format,
channel: *channel,
})
} else {
None
}
}
}

View File

@@ -1,18 +0,0 @@
use wasm_bindgen::prelude::*;
use web_sys::{window, CustomEvent, CustomEventInit};
pub(crate) fn send_custom_event(name: &str, value: JsValue) {
// Create event details (optional)
let mut event_init = CustomEventInit::new();
event_init.detail(&value);
// Create the event
let event = CustomEvent::new_with_event_init_dict(name, &event_init)
.expect("Failed to create custom event");
// Dispatch the event on the window or any target element
window()
.expect("no global `window` exists")
.dispatch_event(&event)
.expect("failed to dispatch event");
}

View File

@@ -15,7 +15,6 @@ use healpix::compass_point::MainWind;
use healpix::compass_point::Ordinal;
use healpix::compass_point::OrdinalMap;
use crate::math::lonlat::LonLatT;
use crate::utils;
impl HEALPixCell {
@@ -96,13 +95,6 @@ impl HEALPixCell {
self.depth() == 0
}
#[inline(always)]
pub fn hash_with_dxdy(depth: u8, lon: f64, lat: f64) -> (Self, f64, f64) {
let (hash, dx, dy) = healpix::nested::hash_with_dxdy(depth, lon, lat);
(HEALPixCell(depth, hash), dx, dy)
}
// Find the smallest HEALPix cell containing self and another cells
// Returns None if the 2 HEALPix cell are not located in the same base HEALPix cell
#[inline]
@@ -491,83 +483,6 @@ impl Ord for HEALPixCell {
}
}
/// A simple object describing a cubic tile of a HiPS3D
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
pub struct HEALPixFreqCell {
pub hpx: HEALPixCell,
pub f_hash: u64,
pub f_depth: u8,
}
use crate::math::spectra::Freq;
use crate::math::spectra::SpectralUnit;
impl HEALPixFreqCell {
pub fn from_lonlat(lonlat: LonLatT<f64>, freq: Freq, s_depth: u8, f_depth: u8) -> Self {
let hpx = HEALPixCell::new(
s_depth,
lonlat.lon().to_radians(),
lonlat.lat().to_radians(),
);
let f_hash = freq.hash(f_depth);
Self {
hpx,
f_hash,
f_depth,
}
}
pub fn new(hpx: HEALPixCell, f_hash: u64, f_depth: u8) -> Self {
Self {
hpx,
f_hash,
f_depth,
}
}
pub fn hpx_parent(&self) -> Self {
Self {
hpx: self.hpx.parent(),
f_hash: self.f_hash,
f_depth: self.f_depth,
}
}
pub fn parent(&self) -> Self {
Self {
hpx: self.hpx.parent(),
f_hash: self.f_hash >> 1,
f_depth: self.f_depth - 1,
}
}
pub fn is_hpx_root(&self) -> bool {
self.hpx.is_root()
}
pub fn freq_range(&self) -> Range<Freq> {
let f0 = Freq::from_hash_with_order(self.f_hash, self.f_depth);
let f1 = Freq::from_hash_with_order(
(self.f_hash + 1).min(Freq::num_max_cells(self.f_depth) as u64),
self.f_depth,
);
f0..f1
}
pub fn pixel_frequencies(&self, num_pixels: usize) -> impl Iterator<Item = f32> {
let delta_depth = num_pixels.trailing_zeros();
let pixel_depth = self.f_depth + delta_depth as u8;
let h0 = self.f_hash << delta_depth;
let h1 = (self.f_hash + 1) << delta_depth;
(h0..h1).map(move |hash| Freq::from_hash_with_order(hash, pixel_depth).0 as f32)
}
}
// Utils
#[inline(always)]
pub fn nside2depth(nside: u32) -> u8 {

View File

@@ -1,9 +1,23 @@
use crate::healpix::cell::HEALPixFreqCell;
use crate::math::lonlat::LonLatT;
use crate::math::PI;
use crate::math::{self, lonlat::LonLat};
use cgmath::Vector3;
use moclib::elemset::range::uniq::HpxUniqRanges;
use moclib::hpxranges2d::HpxRanges2D;
use moclib::moc::RangeMOCIntoIterator;
use moclib::moc2d::{HasTwoMaxDepth, RangeMOC2Iterator};
use moclib::ranges::ranges2d::Ranges2D;
use moclib::{
moc::range::{CellSelection, RangeMOC},
moc2d::range::RangeMOC2,
qty::Hpx,
ranges::SNORanges,
};
use moclib::qty::{Frequency, MocQty};
use moclib::qty::Frequency;
use crate::healpix::cell::HEALPixCell;
#[derive(Debug)]
pub struct FreqSpaceMoc(pub moclib::hpxranges2d::FreqSpaceMoc<u64, u64>);
@@ -23,17 +37,18 @@ use wasm_bindgen::JsValue;
use moclib::deser::fits;
use moclib::deser::fits::MocIdxType;
use moclib::deser::fits::MocQtyType;
use moclib::deser::fits::RangeMoc2DIterFromFits;
use moclib::idx::Idx;
use moclib::moc::range::op::convert::convert_to_u64;
use moclib::moc::{CellMOCIntoIterator, CellMOCIterator, RangeMOCIterator};
use moclib::mocranges2d::Moc2DRanges;
use moclib::deser::fits::MocType;
use std::io::Cursor;
use crate::math::spectra::Freq;
use crate::math::spectra::SpectralUnit;
impl FreqSpaceMoc {
/// Create a FreqSpaceMoc from a
pub fn from_space_moc(moc: SpaceMoc) -> Self {
let moc_2d = Moc2DRanges::new(vec![0..u64::MAX; 1], vec![moc.0.into_moc_ranges().0]);
FreqSpaceMoc(HpxRanges2D(moc_2d))
}
pub fn from_fits_raw_bytes(bytes: &[u8]) -> Result<Self, JsValue> {
let sfmoc = match fits::from_fits_ivoa_custom(Cursor::new(bytes), true)
.map_err(|e| JsValue::from_str(&e.to_string()))?
@@ -41,10 +56,6 @@ impl FreqSpaceMoc {
//MocIdxType::U16(MocQtyType::<u16, _>::FreqHpx(moc)) => Ok(from_fits_hpx(moc)),
//MocIdxType::U32(MocQtyType::<u32, _>::FreqHpx(moc)) => Ok(from_fits_hpx(moc)),
MocIdxType::U64(MocQtyType::<u64, _>::FreqHpx(ranges_iter)) => {
/*al_core::log(&format!(
"ranges moc 2D iter from fits {:?}",
));*/
let moc_2d_ranges = Moc2DRanges::from_ranges_it(ranges_iter);
let inner = moclib::hpxranges2d::HpxRanges2D(moc_2d_ranges);
Ok(inner)
@@ -57,6 +68,44 @@ impl FreqSpaceMoc {
Ok(Self(sfmoc))
}
/// This methods builds a SFMOC made of:
/// * the cells in the spatial viewport at a specific frequency f
/// * the +/- f_window cells containing inside lonlat on the frequency axis
/// This is the method to use when looking for new cube HiPS3D tiles
pub fn from_coos_freq<L: LonLat<f64>, F: SpectralUnit>(
// The depth of the smallest HEALPix cells contained in it
depth: u8,
// The vertices of the polygon delimiting the coverage
vertices_iter: impl Iterator<Item = L>,
// A vertex being inside the coverage,
// typically the center of projection
inside: &L,
// The freq at which we want to compute the sfmoc
f: F,
// Frequency window i.e. the number of cells around f to query
f_window: u8,
) -> Self {
let freq: Freq = f.into();
todo!();
/*let lonlat = vertices_iter
.map(|vertex| {
let LonLatT(lon, lat) = vertex.lonlat();
(lon.to_radians(), lat.to_radians())
})
.collect::<Vec<_>>();
let LonLatT(in_lon, in_lat) = inside.lonlat();
let moc = RangeMOC2::from_freqranges_in_hz_and_coos(
&lonlat[..],
(in_lon.to_radians(), in_lat.to_radians()),
depth,
CellSelection::All,
);
SpaceFreqMoc(moc)*/
}
/*pub fn from_fixed_hpx_cells(
depth: u8,
hpx_idx: impl Iterator<Item = u64>,
@@ -89,28 +138,11 @@ impl FreqSpaceMoc {
todo!()
}
pub fn intersects_cell(&self, cell: &HEALPixFreqCell) -> bool {
let HEALPixFreqCell {
hpx,
f_hash,
f_depth,
} = *cell;
pub fn intersects_cell(&self, hpx_cell: &HEALPixCell, f: Freq) -> bool {
let z29_rng = hpx_cell.z_29_rng();
let f_hash = f.hash();
let f_hash_0 = f_hash << (Frequency::<u64>::MAX_DEPTH - f_depth);
let f_hash_1 = (f_hash + 1) << (Frequency::<u64>::MAX_DEPTH - f_depth);
//let f0 = Frequency::<u64>::hash2freq(5171582628058365952);
//let f1 = Frequency::<u64>::hash2freq(5171590187200806912);
//al_core::log(&format!("F1: {f0}"));
let hpx_ranges_2d = HpxRanges2D::create_from_freq_ranges_positions(
vec![f_hash_0..f_hash_1; 1],
vec![hpx.idx()],
Frequency::<u64>::MAX_DEPTH,
hpx.depth(),
);
!self.0.intersection(&hpx_ranges_2d).is_empty()
self.0.contains(f_hash, &z29_rng)
}
/*/// provide the list of (hash hpx, hash freq) of the cells contained in the sfmoc
@@ -120,8 +152,6 @@ impl FreqSpaceMoc {
}
use core::ops::Deref;
use super::SpaceMoc;
impl Deref for FreqSpaceMoc {
type Target = moclib::hpxranges2d::FreqSpaceMoc<u64, u64>;

View File

@@ -1,7 +1,8 @@
use crate::math::lonlat::LonLat;
use crate::math::lonlat::LonLatT;
use crate::math::PI;
use crate::math::{self, lonlat::LonLat};
use cgmath::Vector3;
use moclib::moc::RangeMOCIntoIterator;
use moclib::{
moc::range::{CellSelection, RangeMOC},
@@ -61,12 +62,14 @@ impl SpaceMoc {
pub fn serialize_to_json(&self) -> Result<String, JsValue> {
let mut buf: Vec<u8> = Default::default();
(&self.0)
let json = (&self.0)
.into_range_moc_iter()
.cells()
.to_json_aladin(None, &mut buf)
.map(|()| unsafe { String::from_utf8_unchecked(buf) })
.map_err(|err| JsValue::from_str(&format!("{err:?}")))
.map_err(|err| JsValue::from_str(&format!("{err:?}")));
json
}
pub fn from_3d_coos<T: LonLat<f64>>(

View File

@@ -39,7 +39,7 @@ pub trait Abort {
impl<T> Abort for Option<T> {
type Item = T;
#[inline(always)]
#[inline]
fn unwrap_abort(self) -> Self::Item {
use std::process;
match self {
@@ -51,7 +51,7 @@ impl<T> Abort for Option<T> {
impl<T, E> Abort for Result<T, E> {
type Item = T;
#[inline(always)]
#[inline]
fn unwrap_abort(self) -> Self::Item {
use std::process;
match self {
@@ -65,7 +65,7 @@ extern crate serde_json;
#[macro_use]
extern crate enum_dispatch;
#[inline(always)]
#[inline]
pub fn unwrap_abort<T>(o: Option<T>) -> T {
use std::process;
match o {
@@ -85,6 +85,7 @@ mod utils;
use math::projection::*;
use moclib::moc::RangeMOCIntoIterator;
//use votable::votable::VOTableWrapper;
use crate::tile_fetcher::HiPSLocalFiles;
use al_api::moc::MOCOptions;
@@ -100,10 +101,8 @@ pub mod async_task;
mod camera;
mod shaders;
mod browser_support;
mod coosys;
mod downloader;
mod event;
mod fifo_cache;
mod healpix;
mod inertia;
@@ -118,6 +117,8 @@ use crate::{
time::DeltaTime,
};
use std::io::Cursor;
use al_api::color::{Color, ColorRGBA};
use al_api::coo_system::CooSystem;
use al_api::hips::HiPSProperties;
@@ -412,24 +413,9 @@ impl WebClient {
self.app.set_image_hips_color_cfg(layer, meta)
}
#[wasm_bindgen(js_name = setFreq)]
pub fn set_hips_frequency(&mut self, layer: String, frequency: f32) -> Result<(), JsValue> {
self.app.set_hips_frequency(&layer, frequency)
}
#[wasm_bindgen(js_name = getFreq)]
pub fn get_hips_frequency(&mut self, layer: String) -> Result<f32, JsValue> {
self.app.get_hips_frequency(&layer)
}
#[wasm_bindgen(js_name = freq2hash)]
pub fn get_freq_hash(&mut self, layer: String, freq: f64) -> Result<u64, JsValue> {
self.app.get_freq_hash(&layer, freq)
}
#[wasm_bindgen(js_name = hash2freq)]
pub fn get_freq_from_hash(&mut self, layer: String, hash: u64) -> Result<f64, JsValue> {
self.app.get_freq_from_hash(&layer, hash)
#[wasm_bindgen(js_name = setSliceNumber)]
pub fn set_hips_slice_number(&mut self, layer: String, slice: u32) -> Result<(), JsValue> {
self.app.set_hips_slice_number(&layer, slice)
}
#[wasm_bindgen(js_name = setBackgroundColor)]

View File

@@ -55,15 +55,6 @@ impl From<fitsrs::wcs::LonLat> for LonLatT<f64> {
}
}
impl<S: BaseFloat> From<&'_ Vector3<S>> for LonLatT<S> {
fn from(v: &'_ Vector3<S>) -> Self {
let lon = Rad(v.x.atan2(v.z));
let lat = Rad(v.y.atan2((v.x * v.x + v.z * v.z).sqrt()));
LonLatT::new(Angle::new(lon), Angle::new(lat))
}
}
impl<S> LonLat<S> for LonLatT<S>
where
S: BaseFloat,
@@ -107,7 +98,10 @@ where
#[inline]
fn lonlat(&self) -> LonLatT<S> {
self.into()
let lon = Rad(self.x.atan2(self.z));
let lat = Rad(self.y.atan2((self.x * self.x + self.z * self.z).sqrt()));
LonLatT::new(Angle::new(lon), Angle::new(lat))
}
#[inline]

View File

@@ -1,69 +1,27 @@
pub trait SpectralUnit: Into<Freq> + Clone + Copy {
fn hash(&self, depth: u8) -> u64 {
fn hash(&self) -> u64 {
let f: Freq = (*self).into();
let f_hash_max_order = Frequency::<u64>::freq2hash(f.0);
f_hash_max_order >> (Frequency::<u64>::MAX_DEPTH - depth)
Frequency::freq2hash(f.0)
}
}
use moclib::qty::{Frequency, MocQty};
pub const FREQ_MAX: Freq = Freq(5.846_006_549_323_611e48);
pub const FREQ_MIN: Freq = Freq(5.048_709_793_414_476e-29);
use moclib::qty::Frequency;
/// Frequency in Hz unit
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
#[derive(Clone, Copy)]
pub struct Freq(pub f64);
impl Freq {
pub fn from_hash(hash: u64) -> Self {
fn from_hash(hash: u64) -> Self {
let f = Frequency::hash2freq(hash);
Freq(f)
}
pub fn from_hash_with_order(hash: u64, order: u8) -> Self {
let hash_max_order = hash << (Frequency::<u64>::MAX_DEPTH - order);
let f = Frequency::hash2freq(hash_max_order);
Freq(f)
}
pub fn max(&self, other: Self) -> Self {
Freq(self.0.max(other.0))
}
pub fn min(&self, other: Self) -> Self {
Freq(self.0.min(other.0))
}
pub fn num_max_cells(order: u8) -> usize {
(Frequency::<u64>::n_cells_max() >> (Frequency::<u64>::MAX_DEPTH - order)) as usize
}
}
use std::ops::Sub;
impl Sub for Freq {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self(self.0 - other.0)
}
}
use std::ops::Add;
impl Add for Freq {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self(self.0 + other.0)
}
}
/// Wavelength in meter unit
#[derive(Clone, Copy)]
pub struct Wavelength(pub f64);
pub struct Wavelength(f64);
/// Velocity in meter/sec unit
#[derive(Clone, Copy)]

View File

@@ -198,15 +198,14 @@ impl ProjetedGrid {
if self.enabled {
let fov = camera.get_field_of_view();
let bbox = fov.get_bounding_box();
//let max_dim_px = camera.get_width().max(camera.get_height()) as f64;
//let step_line_px = max_dim_px * 0.15;
let aspect = camera.get_aspect() as f64;
let max_dim_px = camera.get_width().max(camera.get_height()) as f64;
let step_line_px = max_dim_px * 0.2;
// update meridians
self.meridians = {
// Select the good step with a binary search
let step_lon_precised = bbox.get_lon_size() * 0.15;
let step_lon_precised =
bbox.get_lon_size() * step_line_px / (camera.get_width() as f64);
let step_lon = select_fixed_step(step_lon_precised);
let decimal_lon_prec = step_lon.to_degrees().log10().abs().ceil() as u8;
@@ -236,7 +235,8 @@ impl ProjetedGrid {
};
self.parallels = {
let step_lat_precised = aspect * bbox.get_lat_size() * 0.15;
let step_lat_precised =
bbox.get_lat_size() * step_line_px / (camera.get_height() as f64);
let step_lat = select_fixed_step(step_lat_precised);
let decimal_lat_prec = step_lat.to_degrees().log10().abs().ceil() as u8;
@@ -349,7 +349,7 @@ const GRID_STEPS: &[f64] = &[
0.08726647,
0.17453293,
0.34906585,
std::f64::consts::FRAC_PI_6,
std::f64::consts::FRAC_PI_4,
];
fn select_fixed_step(fov: f64) -> f64 {

View File

@@ -1,9 +1,9 @@
use al_api::hips::{DataproductType, ImageExt};
use crate::math::spectra::Freq;
use al_core::image::format::ImageFormatType;
use al_core::texture::format::PixelType;
use web_sys::{RequestCredentials, RequestMode};
#[derive(Debug)]
pub struct HiPSConfig {
pub root_url: String,
@@ -14,19 +14,14 @@ pub struct HiPSConfig {
pub tile_size: i32,
// Number of slices for HiPS cubes
pub cube_depth: Option<u32>,
cube_depth: Option<u32>,
/// Max depth of the current HiPS tiles
pub max_depth_tile: u8,
/// Min depth of the current HiPS tiles
// Max depth of the current HiPS tiles
max_depth_tile: u8,
// Min depth of the current HiPS tiles
min_depth_tile: u8,
/// Max depth in the frequency axis (HiPS3D only)
pub max_depth_freq: Option<u8>,
/// Start of spectral coordinates (in meters)
pub em_min: Option<Freq>,
/// End of spectral coordinates (in meters)
pub em_max: Option<Freq>,
// For HiPS3D
max_depth_freq: Option<u8>,
// For HiPS3D
pub tile_depth: Option<u8>,
@@ -46,7 +41,7 @@ pub struct HiPSConfig {
pub request_mode: RequestMode,
}
use crate::{math::spectra::Wavelength, HiPSProperties};
use crate::HiPSProperties;
use al_api::coo_system::CooSystem;
use wasm_bindgen::JsValue;
@@ -86,7 +81,6 @@ impl HiPSConfig {
32 => Ok(PixelType::R32I),
-32 => Ok(PixelType::R32F),
-64 => Ok(PixelType::R32F),
64 => Ok(PixelType::R32I),
_ => Err(JsValue::from_str(
"Fits tiles exists but the BITPIX is not correct in the property file",
)),
@@ -136,13 +130,6 @@ impl HiPSConfig {
let max_depth_freq = properties.get_hips_order_freq();
let tile_depth = properties.get_hips_tile_depth();
let em_min: Option<Freq> = properties
.get_em_max()
.map(|lambda| Wavelength(lambda as f64).into());
let em_max: Option<Freq> = properties
.get_em_min()
.map(|lambda| Wavelength(lambda as f64).into());
let hips_config = HiPSConfig {
creator_did,
// HiPS name
@@ -155,9 +142,6 @@ impl HiPSConfig {
// HiPSCube
cube_depth,
em_min,
em_max,
// HiPS3D
tile_depth,
max_depth_freq,
@@ -183,7 +167,6 @@ impl HiPSConfig {
8 => Ok(PixelType::R8U),
16 => Ok(PixelType::R16I),
32 => Ok(PixelType::R32I),
64 => Ok(PixelType::R32I),
-32 => Ok(PixelType::R32F),
-64 => Ok(PixelType::R32F),
_ => Err(JsValue::from_str(
@@ -223,7 +206,6 @@ impl HiPSConfig {
self.root_url = root_url;
}
#[inline(always)]
pub fn get_cube_depth(&self) -> Option<u32> {
self.cube_depth
}

View File

@@ -1,9 +1,10 @@
use crate::renderable::hips::d2::texture::HpxTex;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use al_core::texture::format::PixelType;
use crate::downloader::request::allsky::AllskyRequest;
use crate::renderable::hips::HpxTile;
use cgmath::Vector3;
use al_api::hips::ImageExt;
@@ -15,8 +16,9 @@ use al_core::texture::format::{R16I, R32F, R32I, R8U, RGB8U, RGBA8U};
use al_core::Texture2DArray;
use al_core::WebGlContext;
use super::texture::HpxTexUniforms;
use super::texture::{HpxTexture2D, HpxTexture2DUniforms};
use crate::downloader::request::allsky::Allsky;
use crate::healpix::cell::HEALPixCell;
use crate::healpix::cell::NUM_HPX_TILES_DEPTH_ZERO;
use crate::renderable::hips::config::HiPSConfig;
@@ -24,19 +26,116 @@ use crate::time::Time;
use crate::Abort;
use crate::JsValue;
use super::super::tile_heap::{Tile, TileHeap};
#[derive(Clone, Debug)]
pub struct TextureCellItem {
cell: HEALPixCell,
time_request: Time,
}
impl TextureCellItem {
fn is_root(&self) -> bool {
self.cell.is_root()
}
}
impl PartialEq for TextureCellItem {
fn eq(&self, other: &Self) -> bool {
self.cell == other.cell
}
}
impl Eq for TextureCellItem {}
// Ordering based on the time the tile has been requested
impl PartialOrd for TextureCellItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TextureCellItem {
fn cmp(&self, other: &Self) -> Ordering {
other
.time_request
.partial_cmp(&self.time_request)
.unwrap_abort()
}
}
impl From<HpxTexture2D> for TextureCellItem {
fn from(texture: HpxTexture2D) -> Self {
let time_request = texture.time_request();
let cell = *texture.cell();
Self { cell, time_request }
}
}
impl From<&HpxTexture2D> for TextureCellItem {
fn from(texture: &HpxTexture2D) -> Self {
let time_request = texture.time_request();
let cell = *texture.cell();
Self { cell, time_request }
}
}
impl From<&mut HpxTexture2D> for TextureCellItem {
fn from(texture: &mut HpxTexture2D) -> Self {
let time_request = texture.time_request();
let cell = *texture.cell();
Self { cell, time_request }
}
}
struct HEALPixCellHeap(BinaryHeap<TextureCellItem>);
impl HEALPixCellHeap {
fn with_capacity(cap: usize) -> Self {
Self(BinaryHeap::with_capacity(cap))
}
fn push<E: Into<TextureCellItem>>(&mut self, item: E) {
let item = item.into();
self.0.push(item);
}
fn update_entry<E: Into<TextureCellItem>>(&mut self, item: E) {
let item = item.into();
self.0 = self
.0
.drain()
// Remove the cell
.filter(|texture_node| texture_node.cell != item.cell)
// Collect to a new binary heap that does not have cell anymore
.collect::<BinaryHeap<_>>();
self.push(item);
}
fn clear(&mut self) {
self.0.clear();
}
fn pop(&mut self) -> Option<TextureCellItem> {
self.0.pop()
}
fn len(&self) -> usize {
self.0.len()
}
}
use crate::renderable::hips::HpxTileBuffer;
// Fixed sized binary heap
pub struct HiPS2DBuffer {
// Some information about the HiPS
config: HiPSConfig,
heap: TileHeap<HEALPixCell>,
textures: HashMap<HEALPixCell, HpxTex>,
heap: HEALPixCellHeap,
num_root_textures_available: u8,
size: usize,
base_textures: [HpxTex; NUM_HPX_TILES_DEPTH_ZERO],
textures: HashMap<HEALPixCell, HpxTexture2D>,
base_textures: [HpxTexture2D; NUM_HPX_TILES_DEPTH_ZERO],
// Array of 2D textures
tile_pixels: Texture2DArray,
@@ -119,14 +218,16 @@ fn create_hpx_texture_storage(
}
impl HiPS2DBuffer {
pub fn push_allsky(&mut self, allsky: AllskyRequest) -> Result<(), JsValue> {
let AllskyRequest { request, .. } = allsky;
pub fn push_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> {
let Allsky {
image, time_req, ..
} = allsky;
{
let mutex_locked = request.data.borrow();
let mutex_locked = image.borrow();
let images = mutex_locked.as_ref().unwrap_abort();
for (idx, image) in images.iter().enumerate() {
self.push(&HEALPixCell(0, idx as u64), image, request.time_request)?;
self.push(&HEALPixCell(0, idx as u64), image, time_req)?;
}
}
@@ -149,6 +250,14 @@ impl HiPS2DBuffer {
}
}
fn is_heap_full(&self) -> bool {
// Check that there are no more than num_textures
// textures in the buffer
let num_textures_heap = self.heap.len();
num_textures_heap == self.size
}
// Update the priority of the texture containing the tile
// It must be ensured that the tile is already contained in the buffer
pub fn update_priority(&mut self, cell: &HEALPixCell /*, new_fov_cell: bool*/) {
@@ -161,7 +270,7 @@ impl HiPS2DBuffer {
let texture = self
.textures
.get(cell)
.get_mut(cell)
.expect("Texture cell has not been found while the buffer contains one of its tile!");
// Reset the time the tile has been received if it is a new cell present in the fov
//if new_fov_cell {
@@ -174,8 +283,8 @@ impl HiPS2DBuffer {
// But other textures can be removed thanks to the heap
// data-structure. We have to update the time_request of the texture
// and push it again in the heap to update its position.
let mut tex_cell_item: Tile<HEALPixCell> = texture.into();
tex_cell_item.reset_time();
let mut tex_cell_item: TextureCellItem = texture.into();
tex_cell_item.time_request = Time::now();
self.heap.update_entry(tex_cell_item);
}
@@ -222,14 +331,14 @@ impl HiPS2DBuffer {
if !self.contains_tile(cell) {
// The texture is not among the essential ones
// (i.e. is not a root texture)
let mut texture = if self.heap.is_full() {
let mut texture = if self.is_heap_full() {
// Pop the oldest requested texture
let oldest_texture = self.heap.pop().unwrap_abort();
// Ensure this is not a base texture
debug_assert!(!oldest_texture.is_root());
// Remove it from the textures HashMap
let mut texture = self.textures.remove(oldest_texture.cell()).expect(
let mut texture = self.textures.remove(&oldest_texture.cell).expect(
"Texture (oldest one) has not been found in the buffer of textures",
);
texture.replace(cell, time_request);
@@ -238,7 +347,7 @@ impl HiPS2DBuffer {
} else {
let idx = NUM_HPX_TILES_DEPTH_ZERO + self.heap.len();
HpxTex::new(cell, idx as i32, time_request)
HpxTexture2D::new(cell, idx as i32, time_request)
};
texture.copy_to_gpu(
@@ -312,26 +421,10 @@ impl HiPS2DBuffer {
pub fn render_allsky(&mut self, flag: bool) {
self.allsky_rendering = flag;
}
// Get the nearest parent tile found in the CPU buffer
pub fn get_nearest_parent(&self, cell: &HEALPixCell) -> Option<HEALPixCell> {
let mut parent_cell = cell.parent();
while !self.contains(&parent_cell) && !parent_cell.is_root() {
parent_cell = parent_cell.parent();
}
if self.contains(&parent_cell) {
Some(parent_cell)
} else {
None
}
}
}
impl HpxTileBuffer for HiPS2DBuffer {
type T = HpxTex;
type C = HEALPixCell;
type T = HpxTexture2D;
fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<Self, JsValue> {
let size = 128 - NUM_HPX_TILES_DEPTH_ZERO;
@@ -339,23 +432,23 @@ impl HpxTileBuffer for HiPS2DBuffer {
// Ensures there is at least space for the 12
// root textures
//debug_assert!(size >= NUM_HPX_TILES_DEPTH_ZERO);
let heap = TileHeap::with_capacity(size);
let heap = HEALPixCellHeap::with_capacity(size);
let textures = HashMap::with_capacity(size);
let now = Time::now();
let base_textures = [
HpxTex::new(&HEALPixCell(0, 0), 0, now),
HpxTex::new(&HEALPixCell(0, 1), 1, now),
HpxTex::new(&HEALPixCell(0, 2), 2, now),
HpxTex::new(&HEALPixCell(0, 3), 3, now),
HpxTex::new(&HEALPixCell(0, 4), 4, now),
HpxTex::new(&HEALPixCell(0, 5), 5, now),
HpxTex::new(&HEALPixCell(0, 6), 6, now),
HpxTex::new(&HEALPixCell(0, 7), 7, now),
HpxTex::new(&HEALPixCell(0, 8), 8, now),
HpxTex::new(&HEALPixCell(0, 9), 9, now),
HpxTex::new(&HEALPixCell(0, 10), 10, now),
HpxTex::new(&HEALPixCell(0, 11), 11, now),
HpxTexture2D::new(&HEALPixCell(0, 0), 0, now),
HpxTexture2D::new(&HEALPixCell(0, 1), 1, now),
HpxTexture2D::new(&HEALPixCell(0, 2), 2, now),
HpxTexture2D::new(&HEALPixCell(0, 3), 3, now),
HpxTexture2D::new(&HEALPixCell(0, 4), 4, now),
HpxTexture2D::new(&HEALPixCell(0, 5), 5, now),
HpxTexture2D::new(&HEALPixCell(0, 6), 6, now),
HpxTexture2D::new(&HEALPixCell(0, 7), 7, now),
HpxTexture2D::new(&HEALPixCell(0, 8), 8, now),
HpxTexture2D::new(&HEALPixCell(0, 9), 9, now),
HpxTexture2D::new(&HEALPixCell(0, 10), 10, now),
HpxTexture2D::new(&HEALPixCell(0, 11), 11, now),
];
let channel = config.get_format().get_pixel_format();
let tile_size = config.get_tile_size();
@@ -374,6 +467,7 @@ impl HpxTileBuffer for HiPS2DBuffer {
config,
heap,
size,
num_root_textures_available,
textures,
base_textures,
@@ -398,18 +492,18 @@ impl HpxTileBuffer for HiPS2DBuffer {
let now = Time::now();
self.base_textures = [
HpxTex::new(&HEALPixCell(0, 0), 0, now),
HpxTex::new(&HEALPixCell(0, 1), 1, now),
HpxTex::new(&HEALPixCell(0, 2), 2, now),
HpxTex::new(&HEALPixCell(0, 3), 3, now),
HpxTex::new(&HEALPixCell(0, 4), 4, now),
HpxTex::new(&HEALPixCell(0, 5), 5, now),
HpxTex::new(&HEALPixCell(0, 6), 6, now),
HpxTex::new(&HEALPixCell(0, 7), 7, now),
HpxTex::new(&HEALPixCell(0, 8), 8, now),
HpxTex::new(&HEALPixCell(0, 9), 9, now),
HpxTex::new(&HEALPixCell(0, 10), 10, now),
HpxTex::new(&HEALPixCell(0, 11), 11, now),
HpxTexture2D::new(&HEALPixCell(0, 0), 0, now),
HpxTexture2D::new(&HEALPixCell(0, 1), 1, now),
HpxTexture2D::new(&HEALPixCell(0, 2), 2, now),
HpxTexture2D::new(&HEALPixCell(0, 3), 3, now),
HpxTexture2D::new(&HEALPixCell(0, 4), 4, now),
HpxTexture2D::new(&HEALPixCell(0, 5), 5, now),
HpxTexture2D::new(&HEALPixCell(0, 6), 6, now),
HpxTexture2D::new(&HEALPixCell(0, 7), 7, now),
HpxTexture2D::new(&HEALPixCell(0, 8), 8, now),
HpxTexture2D::new(&HEALPixCell(0, 9), 9, now),
HpxTexture2D::new(&HEALPixCell(0, 10), 10, now),
HpxTexture2D::new(&HEALPixCell(0, 11), 11, now),
];
self.heap.clear();
@@ -433,7 +527,7 @@ impl HpxTileBuffer for HiPS2DBuffer {
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
fn contains(&self, cell: &Self::C) -> bool {
fn contains(&self, cell: &HEALPixCell) -> bool {
if let Some(t) = self.get(cell) {
t.is_on_gpu()
} else {
@@ -442,7 +536,7 @@ impl HpxTileBuffer for HiPS2DBuffer {
}
/// Accessors
fn get(&self, cell: &Self::C) -> Option<&Self::T> {
fn get(&self, cell: &HEALPixCell) -> Option<&Self::T> {
if cell.is_root() {
let HEALPixCell(_, idx) = cell;
Some(&self.base_textures[*idx as usize])
@@ -476,7 +570,7 @@ impl SendUniforms for HiPS2DBuffer {
let cell = HEALPixCell(0, idx as u64);
let texture = self.get(&cell).unwrap();
let texture_uniforms = HpxTexUniforms::new(texture, idx as i32);
let texture_uniforms = HpxTexture2DUniforms::new(texture, idx as i32);
shader.attach_uniforms_from(&texture_uniforms);
}

View File

@@ -2,12 +2,9 @@ pub mod buffer;
pub mod texture;
use crate::app::BLENDING_ANIM_DURATION;
use crate::browser_support::BrowserFeaturesSupport;
use crate::downloader::query;
use crate::downloader::query::CellDesc;
use crate::downloader::request::allsky::AllskyRequest;
use crate::math::angle::ToAngle;
use crate::tile_fetcher::TileFetcherQueue;
use crate::renderable::hips::HpxTile;
use al_api::hips::ImageExt;
use al_api::hips::ImageMetadata;
use al_core::colormap::Colormap;
@@ -35,6 +32,7 @@ use crate::camera::CameraViewPort;
use crate::shader::ShaderManager;
use crate::utils;
use crate::downloader::request::allsky::Allsky;
use crate::healpix::{cell::HEALPixCell, moc::SpaceMoc};
use crate::time::Time;
@@ -46,7 +44,7 @@ use std::collections::HashSet;
// to not be too much skewed
use buffer::HiPS2DBuffer;
use texture::HpxTex;
use texture::HpxTexture2D;
use super::raytracing::RayTracer;
use super::uv::{TileCorner, TileUVW};
@@ -65,21 +63,13 @@ pub struct HpxDrawData<'a> {
impl<'a> HpxDrawData<'a> {
fn from_texture(
starting_texture: &HpxTex,
ending_texture: &HpxTex,
starting_texture: &HpxTexture2D,
ending_texture: &HpxTexture2D,
cell: &'a HEALPixCell,
) -> Self {
let uv_0 = TileUVW::new(
cell,
&Some(starting_texture.cell),
starting_texture.idx() as f32,
);
let uv_1 = TileUVW::new(
cell,
&Some(ending_texture.cell),
ending_texture.idx() as f32,
);
let start_time = ending_texture.start_time.unwrap_or(Time::now()).as_millis();
let uv_0 = TileUVW::new(cell, starting_texture);
let uv_1 = TileUVW::new(cell, ending_texture);
let start_time = ending_texture.start_time().as_millis();
Self {
uv_0,
@@ -328,12 +318,10 @@ impl HiPS2D {
})
}
pub fn look_for_new_tiles(
&mut self,
tile_fetcher: &mut TileFetcherQueue,
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport,
) {
pub fn look_for_new_tiles<'a>(
&'a mut self,
camera: &'a CameraViewPort,
) -> Option<impl Iterator<Item = HEALPixCell> + 'a> {
// do not add tiles if the view is already at depth 0
let cfg = self.get_config();
let depth_tile = camera
@@ -342,66 +330,30 @@ impl HiPS2D {
.max(cfg.get_min_depth_tile());
let survey_frame = cfg.get_frame();
let min_tile_depth = cfg.get_min_depth_tile();
let tile_queries_iter = camera
let tile_cells_iter = camera
.get_hpx_cells(depth_tile, survey_frame)
.into_iter()
.filter_map(|tile_cell| {
let make_query = if let Some(moc) = self.moc.as_ref() {
moc.intersects_cell(&tile_cell) && !self.update_priority_tile(&tile_cell)
.filter(move |tile_cell| {
if let Some(moc) = self.moc.as_ref() {
moc.intersects_cell(tile_cell) && !self.update_priority_tile(tile_cell)
} else {
!self.update_priority_tile(&tile_cell)
};
if make_query {
Some(query::Tile::new(
&tile_cell,
self.get_config(),
browser_features_support,
))
} else {
None
!self.update_priority_tile(tile_cell)
}
});
let mut ancestors = HashSet::new();
for tile_query in tile_queries_iter {
match tile_query.cell {
CellDesc::HiPS2D { cell, .. } => {
let tile_cell = cell;
tile_fetcher.append(tile_query);
// check if we are starting aladin lite or not.
// If so we want to retrieve only the tiles in the view and access them
// directly i.e. without blending them with less precised tiles
if tile_fetcher.get_num_tile_fetched() > 0
&& tile_cell.depth() >= min_tile_depth + 3
{
let ancestor_tile_cell = tile_cell.ancestor(3);
ancestors.insert(ancestor_tile_cell);
}
}
_ => unreachable!(),
}
}
for ancestor in ancestors {
if !self.update_priority_tile(&ancestor) {
tile_fetcher.append(query::Tile::new(
&ancestor,
self.get_config(),
browser_features_support,
));
}
}
Some(tile_cells_iter)
}
pub fn contains_tile(&self, cell: &HEALPixCell) -> bool {
self.buffer.contains_tile(cell)
}
pub fn get_tile_query(&self, cell: &HEALPixCell) -> query::Tile {
let cfg = self.get_config();
query::Tile::new(cell, None, cfg)
}
pub fn update(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) {
let raytracing = camera.is_raytracing(projection);
@@ -737,7 +689,7 @@ impl HiPS2D {
}
}
pub fn push_tile<I: Image>(
pub fn add_tile<I: Image>(
&mut self,
cell: &HEALPixCell,
image: I,
@@ -746,7 +698,7 @@ impl HiPS2D {
self.buffer.push(cell, image, time_request)
}
pub fn add_allsky(&mut self, allsky: AllskyRequest) -> Result<(), JsValue> {
pub fn add_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> {
self.buffer.push_allsky(allsky)
}

View File

@@ -4,8 +4,8 @@ use al_core::image::Image;
use al_core::Texture2DArray;
use wasm_bindgen::JsValue;
pub struct HpxTex {
pub cell: HEALPixCell,
pub struct HpxTexture2D {
tile_cell: HEALPixCell,
// Precomputed uniq number
uniq: i32,
// Position of the texture in the buffer
@@ -13,7 +13,7 @@ pub struct HpxTex {
// The time the texture has been received
// If the texture contains multiple tiles, then the receiving time
// is set when all the tiles have been copied to the buffer
pub start_time: Option<Time>,
start_time: Option<Time>,
// The time request of the texture is the time request
// of the first tile being inserted in it
// It is then only given in the constructor of Texture
@@ -22,21 +22,23 @@ pub struct HpxTex {
// texture. But this is too expensive because at each tile inserted
// in the buffer, one should reevalute the priority of the texture
// in the buffer's binary heap.
pub time_request: Time,
time_request: Time,
// Full flag telling the texture has been filled
copied_to_gpu: bool,
}
impl HpxTex {
use crate::renderable::hips::HpxTile;
impl HpxTexture2D {
pub fn new(cell: &HEALPixCell, idx: i32, time_request: Time) -> Self {
let start_time = None;
let copied_to_gpu = false;
let cell = *cell;
let tile_cell = *cell;
let uniq = cell.uniq();
Self {
cell,
tile_cell,
uniq,
time_request,
idx,
@@ -54,13 +56,13 @@ impl HpxTex {
}
// Setter
pub fn replace(&mut self, cell: &HEALPixCell, time_request: Time) {
pub fn replace(&mut self, tile_cell: &HEALPixCell, time_request: Time) {
// Cancel the tasks copying the tiles contained in the texture
// which have not yet been completed.
//self.clear_tasks_in_progress(config, exec);
self.cell = *cell;
self.uniq = cell.uniq();
self.tile_cell = *tile_cell;
self.uniq = tile_cell.uniq();
self.copied_to_gpu = false;
self.start_time = None;
self.time_request = time_request;
@@ -75,7 +77,8 @@ impl HpxTex {
image: &I,
gpu_texture: &Texture2DArray,
) -> Result<(), JsValue> {
debug_assert!(*cell == self.cell);
debug_assert!(*cell == self.tile_cell);
debug_assert!(!self.copied_to_gpu);
self.copied_to_gpu = true;
self.start_time = Some(Time::now());
@@ -84,8 +87,7 @@ impl HpxTex {
}
}
/*
impl HpxTile for HpxTex {
impl HpxTile for HpxTexture2D {
// Getter
// Returns the current time if the texture is not full
fn start_time(&self) -> Time {
@@ -101,43 +103,44 @@ impl HpxTile for HpxTex {
}
fn cell(&self) -> &HEALPixCell {
&self.cell
&self.tile_cell
}
}*/
}
use std::cmp::Ordering;
impl PartialOrd for HpxTex {
impl PartialOrd for HpxTexture2D {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HpxTex {
use crate::Abort;
impl Ord for HpxTexture2D {
fn cmp(&self, other: &Self) -> Ordering {
self.uniq.cmp(&other.uniq)
}
}
impl PartialEq for HpxTex {
impl PartialEq for HpxTexture2D {
fn eq(&self, other: &Self) -> bool {
self.uniq == other.uniq
}
}
impl Eq for HpxTex {}
impl Eq for HpxTexture2D {}
pub struct HpxTexUniforms<'a> {
texture: &'a HpxTex,
pub struct HpxTexture2DUniforms<'a> {
texture: &'a HpxTexture2D,
name: String,
}
impl<'a> HpxTexUniforms<'a> {
pub fn new(texture: &'a HpxTex, idx_texture: i32) -> Self {
impl<'a> HpxTexture2DUniforms<'a> {
pub fn new(texture: &'a HpxTexture2D, idx_texture: i32) -> Self {
let name = format!("textures_tiles[{idx_texture}].");
HpxTexUniforms { texture, name }
HpxTexture2DUniforms { texture, name }
}
}
use al_core::shader::{SendUniforms, ShaderBound};
impl SendUniforms for HpxTexUniforms<'_> {
impl SendUniforms for HpxTexture2DUniforms<'_> {
// Info: These uniforms are used for raytracing drawing mode only
fn attach_uniforms<'b>(&self, shader: &'b ShaderBound<'b>) -> &'b ShaderBound<'b> {
shader
@@ -157,7 +160,7 @@ impl SendUniforms for HpxTexUniforms<'_> {
)
.attach_uniform(
&format!("{}{}", self.name, "start_time"),
&self.texture.start_time.unwrap_or(Time::now()),
&self.texture.start_time(),
);
shader

View File

@@ -3,9 +3,9 @@ use std::collections::HashMap;
use al_core::image::Image;
use al_core::WebGlContext;
use super::super::tile_heap::TileHeap;
use super::texture::HpxFreqTex;
use crate::healpix::cell::HEALPixFreqCell;
use super::texture::HpxTexture3D;
use crate::downloader::request::allsky::Allsky;
use crate::healpix::cell::HEALPixCell;
use crate::renderable::hips::config::HiPSConfig;
use crate::renderable::hips::HpxTileBuffer;
use crate::time::Time;
@@ -13,10 +13,9 @@ use crate::Abort;
use crate::JsValue;
use al_api::hips::ImageExt;
// Fixed sized binary heap
pub struct HiPS3DBuffer {
pub struct HiPSCubeBuffer {
// Some information about the HiPS
textures: HashMap<HEALPixFreqCell, HpxFreqTex>,
heap: TileHeap<HEALPixFreqCell>,
textures: HashMap<HEALPixCell, HpxTexture3D>,
config: HiPSConfig,
@@ -25,176 +24,111 @@ pub struct HiPS3DBuffer {
gl: WebGlContext,
}
impl HiPS3DBuffer {
/*pub fn push_allsky(&mut self, allsky: AllskyRequest) -> Result<(), JsValue> {
let AllskyRequest {
request,
//depth_tile,
channel,
..
} = allsky;
{
let mutex_locked = request.data.borrow();
let images = mutex_locked.as_ref().unwrap_abort();
for (idx, image) in images.iter().enumerate() {
self.push(
&HEALPixCell(0, idx as u64),
image,
request.time_request,
channel.map(|c| c as u16).unwrap_or(0),
)?;
}
}
Ok(())
}*/
/*pub fn find_nearest_slice(&self, cell: &HEALPixCell, slice: u16) -> Option<u16> {
self.get(cell).and_then(|t| t.find_nearest_slice(slice))
}*/
fn push_cell(&mut self, cell: &HEALPixFreqCell, time_request: Time) -> Result<(), JsValue> {
// Check if the cell is not yet contain in the buffer
if !self.contains(cell) {
// If not, add create it and add it to the buffer
if self.heap.is_full() {
// Pop the oldest requested texture
let oldest_texture = self.heap.pop().unwrap_abort();
// Remove it from the textures HashMap
self.textures
.remove(oldest_texture.cell())
.expect("Texture (oldest one) has not been found in the buffer of textures");
}
let texture = HpxFreqTex::new(
cell.clone(),
time_request,
self.config.tile_size as u16,
self.config.tile_depth.unwrap_or(32) as u16,
self.config.get_format().get_pixel_format(),
&self.gl,
)?;
// Push it to the buffer
self.heap.push(&texture);
self.textures.insert(cell.clone(), texture);
};
Ok(())
}
// Push a image slice into the buffer
pub fn push_tile_slice<I: Image>(
&mut self,
cell: &HEALPixFreqCell,
image: I,
time_request: Time,
// this slice index inside the cubic cell
slice_idx: u16,
) -> Result<(), JsValue> {
self.push_cell(cell, time_request)?;
let texture = self.textures.get_mut(cell).unwrap_abort();
// And copy the image in that cubic tile
texture.append_tile_slice(image, slice_idx)?;
self.available_tiles_during_frame = true;
Ok(())
}
pub fn push_tile_from_fits(
&mut self,
cell: &HEALPixFreqCell,
raw_bytes: js_sys::Uint8Array,
size: (u32, u32, u32),
time_request: Time,
) -> Result<(), JsValue> {
self.push_cell(cell, time_request)?;
let texture = self.textures.get_mut(cell).unwrap_abort();
// And copy the image in that cubic tile
texture.set_data_from_fits(raw_bytes, size)?;
self.available_tiles_during_frame = true;
Ok(())
}
pub fn push_tile_from_jpeg(
&mut self,
cell: &HEALPixFreqCell,
decoded_bytes: Box<[u8]>,
size: (u32, u32, u32),
time_request: Time,
) -> Result<(), JsValue> {
self.push_cell(cell, time_request)?;
let texture = self.textures.get_mut(cell).unwrap_abort();
// And copy the image in that cubic tile
texture.set_data_from_jpeg(decoded_bytes, size)?;
self.available_tiles_during_frame = true;
Ok(())
}
pub fn push_tile_from_png(
&mut self,
cell: &HEALPixFreqCell,
decoded_bytes: Box<[u8]>,
size: (u32, u32, u32),
time_request: Time,
) -> Result<(), JsValue> {
self.push_cell(cell, time_request)?;
let texture = self.textures.get_mut(cell).unwrap_abort();
// And copy the image in that cubic tile
texture.set_data_from_png(decoded_bytes, size)?;
self.available_tiles_during_frame = true;
Ok(())
}
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
pub fn contains_slice(
&self,
// the cell to check
cell: &HEALPixFreqCell,
// the idx of one slice inside the cube, has to be in [0; 2^(f_order) - 1]
idx_slice: u16,
) -> bool {
self.get(cell).is_some_and(|t| t.contains_slice(idx_slice))
}
// Get the nearest spatial parent found in the buffer
pub fn get_nearest_parent(&self, cell: &HEALPixFreqCell) -> Option<HEALPixFreqCell> {
let mut parent_cell = cell.parent();
while !self.contains(&parent_cell) && !parent_cell.is_hpx_root() {
parent_cell = parent_cell.parent();
}
if self.contains(&parent_cell) {
Some(parent_cell)
} else {
None
}
}
}
impl HpxTileBuffer for HiPS3DBuffer {
type T = HpxFreqTex;
type C = HEALPixFreqCell;
fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<Self, JsValue> {
impl HiPSCubeBuffer {
pub fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<Self, JsValue> {
let textures = HashMap::new();
let available_tiles_during_frame = false;
let gl = gl.clone();
Ok(Self {
config,
textures,
available_tiles_during_frame,
gl,
})
}
pub fn push_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> {
let Allsky {
image,
time_req,
//depth_tile,
channel,
..
} = allsky;
{
let mutex_locked = image.borrow();
let images = mutex_locked.as_ref().unwrap_abort();
for (idx, image) in images.iter().enumerate() {
self.push(
&HEALPixCell(0, idx as u64),
image,
time_req,
channel.map(|c| c as u16).unwrap_or(0),
)?;
}
}
Ok(())
}
pub fn find_nearest_slice(&self, cell: &HEALPixCell, slice: u16) -> Option<u16> {
self.get(cell).and_then(|t| t.find_nearest_slice(slice))
}
// This method pushes a new downloaded tile into the buffer
// It must be ensured that the tile is not already contained into the buffer
pub fn push<I: Image>(
&mut self,
cell: &HEALPixCell,
image: I,
time_request: Time,
slice_idx: u16,
) -> Result<(), JsValue> {
let tex = if let Some(tex) = self.textures.get_mut(cell) {
tex
} else {
self.textures
.insert(*cell, HpxTexture3D::new(*cell, time_request));
self.textures.get_mut(cell).unwrap()
};
// copy to the 3D textured block
tex.append(image, slice_idx, &self.config, &self.gl)?;
self.available_tiles_during_frame = true;
Ok(())
}
// Return if tiles did become available
pub fn reset_available_tiles(&mut self) -> bool {
let available_tiles_during_frame = self.available_tiles_during_frame;
self.available_tiles_during_frame = false;
available_tiles_during_frame
}
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
pub fn contains_tile(&self, texture_cell: &HEALPixCell, slice: u16) -> bool {
self.get(texture_cell)
.is_some_and(|t| t.contains_slice(slice))
}
/// Accessors
pub fn get(&self, cell: &HEALPixCell) -> Option<&HpxTexture3D> {
self.textures.get(cell)
}
pub fn config(&self) -> &HiPSConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut HiPSConfig {
&mut self.config
}
}
impl HpxTileBuffer for HiPSCubeBuffer {
type T = HpxTexture3D;
fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<Self, JsValue> {
let textures = HashMap::new();
// Limit the number of cached cubes to 256 so approx 256 MB
let heap = TileHeap::with_capacity(1024);
let available_tiles_during_frame = false;
@@ -203,7 +137,6 @@ impl HpxTileBuffer for HiPS3DBuffer {
config,
textures,
heap,
available_tiles_during_frame,
gl,
})
@@ -221,20 +154,21 @@ impl HpxTileBuffer for HiPS3DBuffer {
self.config.set_image_ext(ext)?;
self.textures.clear();
self.heap.clear();
//self.ready = false;
self.available_tiles_during_frame = true;
Ok(())
}
/// Accessors
fn get(&self, cell: &Self::C) -> Option<&HpxFreqTex> {
self.textures.get(cell)
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
fn contains(&self, cell: &HEALPixCell) -> bool {
self.get(cell).is_some()
}
fn contains(&self, cell: &Self::C) -> bool {
self.get(cell).is_some()
/// Accessors
fn get(&self, cell: &HEALPixCell) -> Option<&HpxTexture3D> {
self.textures.get(cell)
}
fn config(&self) -> &HiPSConfig {
@@ -248,17 +182,16 @@ impl HpxTileBuffer for HiPS3DBuffer {
use al_core::shader::SendUniforms;
use al_core::shader::ShaderBound;
impl SendUniforms for HiPS3DBuffer {
impl SendUniforms for HiPSCubeBuffer {
// Send only the allsky textures
fn attach_uniforms<'a>(&self, shader: &'a ShaderBound<'a>) -> &'a ShaderBound<'a> {
shader.attach_uniforms_from(&self.config)
}
}
impl Drop for HiPS3DBuffer {
impl Drop for HiPSCubeBuffer {
fn drop(&mut self) {
// drop all the 3D block textures
self.textures.clear();
self.heap.clear();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,139 +1,21 @@
use crate::time::Time;
use crate::renderable::hips::d2::texture::HpxTexture2D;
use crate::{healpix::cell::HEALPixCell, time::Time};
use crate::renderable::hips::d3::Freq;
use crate::Abort;
use crate::WebGlContext;
use al_core::image::fits::FitsImage;
use al_core::image::raw::ImageBuffer;
use al_core::image::Image;
use al_core::texture::format::{PixelType, R16I, R32F, R32I, R8U};
use al_core::texture::format::{PixelType, R16I, R32F, R32I, R8U, RGB8U, RGBA8U};
use al_core::texture::Texture3D;
use al_core::webgl_ctx::WebGlRenderingCtx;
use cgmath::Vector3;
use fitsrs::hdu::header::Bitpix;
use std::cmp::Ordering;
use std::ops::Range;
use wasm_bindgen::JsValue;
pub enum HpxFreqData {
Fits {
// The raw bytes of the whole cubic FITS file, data big endian
raw_bytes: Box<[u8]>,
// Offset to the data bytes of the cubic tile
data_byte_offset: Range<usize>,
// Number of bytes per pixel (deduced from the bitpix)
bitpix: Bitpix,
// Triming offset indices when reading the data
trim: (u32, u32, u32),
// Naxis
naxis: (u32, u32, u32),
// Scaling value
bscale: f32,
// Offset value
bzero: f32,
// The real size of the cube
size: (u32, u32, u32),
},
Jpeg {
data: Box<[u8]>,
size: (u32, u32, u32),
},
Png {
data: Box<[u8]>,
size: (u32, u32, u32),
},
}
pub enum Pixel {
F32(f32),
I32(i32),
I16(i16),
U8(u8),
}
impl Pixel {
pub fn to_f32(&self) -> f32 {
match *self {
Pixel::F32(v) => v,
Pixel::I16(v) => v as f32,
Pixel::I32(v) => v as f32,
Pixel::U8(v) => v as f32,
}
}
}
impl HpxFreqData {
pub fn read_pixel(&self, x: u32, y: u32, z: u32) -> Option<f32> {
match self {
HpxFreqData::Fits {
raw_bytes,
data_byte_offset,
bitpix,
trim,
naxis,
bscale,
bzero,
size,
} => {
// Do not remember the origin in fits image data is left-down corner
let y = size.1 - y;
let x_in_data = (trim.0..(trim.0 + naxis.0)).contains(&x);
let y_in_data = (trim.1..(trim.1 + naxis.1)).contains(&y);
let z_in_data = (trim.2..(trim.2 + naxis.2)).contains(&z);
if !x_in_data || !y_in_data || !z_in_data {
None
} else {
let x = x - trim.0;
let y = y - trim.1;
let z = z - trim.2;
let data_raw_bytes = &raw_bytes[data_byte_offset.clone()];
let bytes_per_pixel = bitpix.byte_size();
let pixel_bytes_off =
bytes_per_pixel * (x + y * naxis.0 + z * (naxis.0 * naxis.1)) as usize;
let p = &data_raw_bytes[pixel_bytes_off..(pixel_bytes_off + bytes_per_pixel)];
let pixel = match bitpix {
Bitpix::U8 => Pixel::U8(p[0]),
Bitpix::I16 => Pixel::I16(i16::from_be_bytes([p[0], p[1]])),
Bitpix::I32 => Pixel::I32(i32::from_be_bytes([p[0], p[1], p[2], p[3]])),
Bitpix::F32 => Pixel::F32(f32::from_be_bytes([p[0], p[1], p[2], p[3]])),
Bitpix::F64 => Pixel::F32(f64::from_be_bytes([
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
]) as f32),
Bitpix::I64 => Pixel::I32(i64::from_be_bytes([
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
]) as i32),
};
Some(pixel.to_f32() * (*bscale) + (*bzero))
}
}
HpxFreqData::Jpeg { data, size } => {
let pixel_bytes_off = (x + y * size.0 + z * (size.0 * size.1)) as usize;
let p = data[pixel_bytes_off];
Some(p as f32)
}
HpxFreqData::Png { data, size } => {
let pixel_bytes_off = (x + y * size.0 + z * (size.0 * size.1)) as usize;
let p = data[2 * pixel_bytes_off];
Some(p as f32)
}
}
}
}
pub struct HpxFreqTex {
pub cell: HEALPixFreqCell,
pub struct HpxTexture3D {
tile_cell: HEALPixCell,
// Precomputed uniq number
uniq: i32,
// The time the texture has been received
// If the texture contains multiple tiles, then the receiving time
// is set when all the tiles have been copied to the buffer
pub start_time: Option<Time>,
start_time: Option<Time>,
// The time request of the texture is the time request
// of the first tile being inserted in it
// It is then only given in the constructor of Texture
@@ -142,269 +24,272 @@ pub struct HpxFreqTex {
// texture. But this is too expensive because at each tile inserted
// in the buffer, one should reevalute the priority of the texture
// in the buffer's binary heap.
pub time_request: Time,
time_request: Time,
// OLD CODE
// We autorize 512 cubic tiles of size 32 each which allows to store max 16384 slices
//textures: Vec<Option<Texture3D>>,
textures: Vec<Option<Texture3D>>,
// A set of already inserted slices. Each cubic tiles can have 32 slices. The occupancy of the
// slices inside a cubic tile is done with a u32 mask. Limited to 16384 slices
//blocks: [u32; 512],
blocks: [u32; 512],
// sorted index list of 32-length blocks that are not empty
//block_indices: Vec<usize>,
/// The webgl2 3D texture of the cubic tile
pub texture: Texture3D,
data: Option<HpxFreqData>,
// The real image data for accessing the pixel values
//data: ImageType,
/// A bitvector keeping track of the slices that have been inserted into the 3D texture
/// It is limited to a cube depth of 256 (~ to the max texture size).
slice_idx: [u32; 8],
/// Depth of the tile
num_slices: u16,
/// Number of slices copied (concerns only HiPSCube)
num_stored_slices: u16,
block_indices: Vec<usize>,
}
const TEX_PARAMS: &[(u32, u32)] = &[
(
WebGlRenderingCtx::TEXTURE_MIN_FILTER,
WebGlRenderingCtx::NEAREST,
),
(
WebGlRenderingCtx::TEXTURE_MAG_FILTER,
WebGlRenderingCtx::NEAREST,
),
// Prevents s-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_S,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents t-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_T,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents r-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_R,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
];
use crate::renderable::hips::config::HiPSConfig;
use crate::WebGlContext;
use crate::healpix::cell::HEALPixFreqCell;
impl HpxFreqTex {
pub fn new(
// The cubic tile definition to locate the cube in the sky + spectral axis
cell: HEALPixFreqCell,
// The time the request has been made, i.e. when the tile was needed
time_request: Time,
// The size of the cubis tile
tile_size: u16,
// The depth of the cubic tile. Must be a power of two
num_slices: u16,
// pixel format
pixel_format: PixelType,
// The Gl context
gl: &WebGlContext,
) -> Result<Self, JsValue> {
use crate::renderable::hips::HpxTile;
impl HpxTexture3D {
pub fn new(tile_cell: HEALPixCell, time_request: Time) -> Self {
let start_time = None;
let texture = match pixel_format {
// alpha transparency
PixelType::RGBA8U => Texture3D::create_empty::<R16I>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::RGB8U => Texture3D::create_empty::<R8U>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R8U => Texture3D::create_empty::<R8U>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R32F => Texture3D::create_empty::<R32F>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R16I => Texture3D::create_empty::<R16I>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R32I => Texture3D::create_empty::<R32I>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
}?;
let data = None;
let num_stored_slices = 0;
let slice_idx = [0x0; 8];
Ok(Self {
cell,
slice_idx,
let uniq = tile_cell.uniq();
let textures = std::iter::repeat_n(None, 512).collect();
let blocks = [0; 512];
let block_indices = Vec::new();
Self {
tile_cell,
uniq,
time_request,
start_time,
data,
texture,
num_slices,
num_stored_slices,
})
textures,
blocks,
block_indices,
}
}
pub fn set_data_from_fits(
&mut self,
// the tile image of the whole cubic tile
raw_bytes: js_sys::Uint8Array,
// size of the cube
size: (u32, u32, u32),
) -> Result<(), JsValue> {
let raw_bytes = raw_bytes.to_vec().into_boxed_slice();
pub fn find_nearest_slice(&self, slice: u16) -> Option<u16> {
let block_idx = (slice >> 5) as usize;
let (trim1, trim2, trim3, width, height, depth, bitpix, data_byte_offset, bscale, bzero) = {
let fits = FitsImage::from_raw_bytes(&raw_bytes[..])?;
fits[0].insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
match self.block_indices.binary_search(&block_idx) {
Ok(_) => {
if self.contains_slice(slice) {
Some(slice)
} else {
// the slice is not present but we know there is one in the block
let block = self.blocks[block_idx];
(
fits[0].trim1,
fits[0].trim2,
fits[0].trim3,
fits[0].width,
fits[0].height,
fits[0].depth,
fits[0].bitpix,
fits[0].data_byte_offset.clone(),
fits[0].bscale,
fits[0].bzero,
)
};
let slice_idx = (slice & 0x1f) as u32;
let trim = (trim1, trim2, trim3);
let naxis = (width, height, depth);
let m2 = if slice_idx == 31 {
0
} else {
0xffffffff >> (slice_idx + 1)
};
let m1 = (!m2) & !(1 << (31 - slice_idx));
self.data = Some(HpxFreqData::Fits {
raw_bytes,
data_byte_offset: data_byte_offset.clone(),
bitpix,
trim,
naxis,
bscale,
bzero,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());
let lb = (block & m1) >> (32 - slice_idx);
let rb = block & m2;
Ok(())
let lb_trailing_zeros = (lb.trailing_zeros() as u16).min(slice_idx as u16);
let rb_leading_zeros = (rb.leading_zeros() - slice_idx - 1) as u16;
let no_more_left_bits = slice_idx - (lb_trailing_zeros as u32) == 0;
let no_more_right_bits = slice_idx + (rb_leading_zeros as u32) == 31;
match (no_more_left_bits, no_more_right_bits) {
(false, false) => {
if lb_trailing_zeros <= rb_leading_zeros {
Some(slice - lb_trailing_zeros - 1)
} else {
Some(slice + rb_leading_zeros + 1)
}
}
(false, true) => {
if lb_trailing_zeros <= rb_leading_zeros {
Some(slice - lb_trailing_zeros - 1)
} else {
// explore next block
if block_idx == self.blocks.len() - 1 {
// no after block
Some(slice - lb_trailing_zeros - 1)
} else {
// get the next block
let next_block = self.blocks[block_idx + 1];
let num_bits_to_next_block =
next_block.leading_zeros() as u16 + rb_leading_zeros;
if num_bits_to_next_block < lb_trailing_zeros {
Some(slice + num_bits_to_next_block + 1)
} else {
Some(slice - lb_trailing_zeros - 1)
}
}
}
}
(true, false) => {
if rb_leading_zeros <= lb_trailing_zeros {
Some(slice + rb_leading_zeros + 1)
} else {
// explore previous block
if block_idx == 0 {
// no after block
Some(slice + rb_leading_zeros + 1)
} else {
// get the next block
let prev_block = self.blocks[block_idx - 1];
let num_bits_from_prev_block =
prev_block.trailing_zeros() as u16 + lb_trailing_zeros;
if num_bits_from_prev_block < rb_leading_zeros {
Some(slice - num_bits_from_prev_block - 1)
} else {
Some(slice + rb_leading_zeros + 1)
}
}
}
}
(true, true) => unreachable!(),
}
}
}
Err(i) => {
let prev_block = if i > 0 {
self.block_indices.get(i - 1)
} else {
None
};
let cur_block = self.block_indices.get(i);
match (prev_block, cur_block) {
(Some(b_idx_1), Some(b_idx_2)) => {
let b1 = self.blocks[*b_idx_1];
let b2 = self.blocks[*b_idx_2];
let b1_tz = b1.trailing_zeros() as usize;
let b2_lz = b2.leading_zeros() as usize;
let slice_b1 = ((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16;
let slice_b2 = ((*b_idx_2 << 5) + b2_lz) as u16;
if slice - slice_b1 <= slice_b2 - slice {
// the nearest slice is in b1
Some(slice_b1)
} else {
// the nearest slice is in b2
Some(slice_b2)
}
}
(None, Some(b_idx_2)) => {
let b2 = self.blocks[*b_idx_2];
let b2_lz = b2.leading_zeros() as usize;
Some(((*b_idx_2 << 5) + b2_lz) as u16)
}
(Some(b_idx_1), None) => {
let b1 = self.blocks[*b_idx_1];
let b1_tz = b1.trailing_zeros() as usize;
Some(((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16)
}
(None, None) => None,
}
}
}
}
pub fn read_pixel(&self, x: u32, y: u32, z: u32) -> Option<f32> {
if let Some(data) = &self.data {
data.read_pixel(x, y, z)
pub fn get_3d_block_from_slice(&self, slice: u16) -> Option<&Texture3D> {
let block_idx = (slice >> 5) as usize;
self.textures[block_idx].as_ref()
}
pub fn extract_2d_slice_texture(&self, slice: u16) -> Option<HpxTexture2D> {
// Find the good sub cube containing the slice
let block_idx = (slice >> 5) as usize;
let slice_idx = (slice & 0x1f) as u8;
// check the texture is there
if self.blocks[block_idx] & (1 << (31 - slice_idx)) != 0 {
Some(HpxTexture2D::new(
&self.tile_cell,
slice_idx as i32,
self.time_request,
))
} else {
None
}
}
pub fn frequencies(&self) -> Vec<f32> {
let delta_depth = self.num_slices.trailing_zeros();
let pixel_depth = self.cell.f_depth + delta_depth as u8;
let h0 = self.cell.f_hash << delta_depth;
let h1 = (self.cell.f_hash + 1) << delta_depth;
(h0..h1)
.map(|hash| Freq::from_hash_with_order(hash, pixel_depth).0 as f32)
.collect()
}
pub fn set_data_from_jpeg(
&mut self,
// the tile image of the whole cubic tile
decoded_bytes: Box<[u8]>,
// size of the cube
size: (u32, u32, u32),
) -> Result<(), JsValue> {
let cubic_tile = ImageBuffer::<R8U>::new(decoded_bytes, size.0, size.1, size.2);
cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = Some(HpxFreqData::Jpeg {
data: cubic_tile.data,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());
Ok(())
}
pub fn set_data_from_png(
&mut self,
// the tile image of the whole cubic tile
decoded_bytes: Box<[u8]>,
// size of the cube
size: (u32, u32, u32),
) -> Result<(), JsValue> {
let cubic_tile = ImageBuffer::<R16I>::new(decoded_bytes, size.0, size.1, size.2);
cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = Some(HpxFreqData::Png {
data: cubic_tile.data,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());
Ok(())
}
// Panic if cell is not contained in the texture
// Do nothing if the texture is full
// Return true if the tile is newly added
// Used by HiPS Cubes
pub fn append_tile_slice<I: Image>(
pub fn append<I: Image>(
&mut self,
// the tile image of 1 slice
image: I,
// the slice offset in the cubic tile
offset: u16,
slice: u16,
cfg: &HiPSConfig,
gl: &WebGlContext,
) -> Result<(), JsValue> {
// If there is already something, do not tex sub
let block_idx = (offset >> 5) as usize;
let slice_idx = (offset & 0x1f) as u8;
let block_idx = (slice >> 5) as usize;
if self.slice_idx[block_idx] & (1 << (31 - slice_idx)) == 0 {
image.insert_into_3d_texture(
&self.texture,
&Vector3::<i32>::new(0, 0, slice_idx as i32),
)?;
let texture = if let Some(texture) = self.textures[block_idx].as_ref() {
texture
} else {
let tile_size = cfg.get_tile_size();
let params = &[
(
WebGlRenderingCtx::TEXTURE_MIN_FILTER,
WebGlRenderingCtx::NEAREST,
),
(
WebGlRenderingCtx::TEXTURE_MAG_FILTER,
WebGlRenderingCtx::NEAREST,
),
// Prevents s-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_S,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents t-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_T,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents r-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_R,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
];
self.slice_idx[block_idx] |= 1 << (31 - slice_idx);
self.num_stored_slices += 1;
let texture = match cfg.get_format().get_pixel_format() {
PixelType::RGBA8U => {
Texture3D::create_empty::<RGBA8U>(gl, tile_size, tile_size, 32, params)
}
PixelType::RGB8U => {
Texture3D::create_empty::<RGB8U>(gl, tile_size, tile_size, 32, params)
}
PixelType::R32F => {
Texture3D::create_empty::<R32F>(gl, tile_size, tile_size, 32, params)
}
PixelType::R8U => {
Texture3D::create_empty::<R8U>(gl, tile_size, tile_size, 32, params)
}
PixelType::R16I => {
Texture3D::create_empty::<R16I>(gl, tile_size, tile_size, 32, params)
}
PixelType::R32I => {
Texture3D::create_empty::<R32I>(gl, tile_size, tile_size, 32, params)
}
};
self.textures[block_idx] = Some(texture?);
self.textures[block_idx].as_ref().unwrap()
};
let slice_idx = slice & 0x1f;
// if there is already something, do not tex sub
if self.blocks[block_idx] & (1 << (31 - slice_idx)) == 0 {
image.insert_into_3d_texture(texture, &Vector3::<i32>::new(0, 0, slice_idx as i32))?;
match self.block_indices.binary_search(&block_idx) {
Ok(_) => {} // element already in vector @ `pos`
Err(i) => self.block_indices.insert(i, block_idx),
}
self.blocks[block_idx] |= 1 << (31 - slice_idx);
}
self.start_time = Some(Time::now());
@@ -413,29 +298,50 @@ impl HpxFreqTex {
}
// Cell must be contained in the texture
pub fn contains_slice(&self, offset: u16) -> bool {
let block_idx = (offset >> 5) as usize;
let slice_idx = offset & 0x1f;
pub fn contains_slice(&self, slice: u16) -> bool {
let block_idx = (slice >> 5) as usize;
let idx_in_block = slice & 0x1f;
(self.slice_idx[block_idx] >> (31 - slice_idx)) & 0x1 == 1
(self.blocks[block_idx] >> (31 - idx_in_block)) & 0x1 == 1
}
}
impl PartialOrd for HpxFreqTex {
impl HpxTile for HpxTexture3D {
// Getter
// Returns the current time if the texture is not full
fn start_time(&self) -> Time {
if let Some(t) = self.start_time {
t
} else {
Time::now()
}
}
fn time_request(&self) -> Time {
self.time_request
}
fn cell(&self) -> &HEALPixCell {
&self.tile_cell
}
}
use std::cmp::Ordering;
impl PartialOrd for HpxTexture3D {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HpxFreqTex {
use crate::Abort;
impl Ord for HpxTexture3D {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap_abort()
}
}
impl PartialEq for HpxFreqTex {
impl PartialEq for HpxTexture3D {
fn eq(&self, other: &Self) -> bool {
self.cell == other.cell
self.uniq == other.uniq
}
}
impl Eq for HpxFreqTex {}
impl Eq for HpxTexture3D {}

View File

@@ -3,23 +3,23 @@ pub mod config;
pub mod d2;
pub mod d3;
pub mod raytracing;
pub mod tile_heap;
mod triangulation;
pub mod uv;
pub use d2::HiPS2D;
use crate::browser_support::BrowserFeaturesSupport;
use crate::downloader::request::allsky::Allsky;
use crate::renderable::HiPSConfig;
use crate::tile_fetcher::TileFetcherQueue;
use crate::time::Time;
use crate::CameraViewPort;
use crate::HEALPixCell;
use crate::SpaceMoc;
use crate::WebGlContext;
use al_api::hips::ImageExt;
use wasm_bindgen::JsValue;
mod subdivide;
/*
pub(crate) trait HpxTile {
// Getter
// Returns the current time if the texture is not full
@@ -28,11 +28,10 @@ pub(crate) trait HpxTile {
fn time_request(&self) -> Time;
fn cell(&self) -> &HEALPixCell;
}*/
}
pub(crate) trait HpxTileBuffer {
type T;
type C;
type T: HpxTile;
fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<Self, JsValue>
where
@@ -44,14 +43,35 @@ pub(crate) trait HpxTileBuffer {
fn reset_available_tiles(&mut self) -> bool;
/// Accessors
fn get(&self, cell: &Self::C) -> Option<&Self::T>;
fn get(&self, cell: &HEALPixCell) -> Option<&Self::T>;
fn contains(&self, cell: &Self::C) -> bool;
fn contains(&self, cell: &HEALPixCell) -> bool;
// Get the nearest parent tile found in the CPU buffer
fn get_nearest_parent(&self, cell: &HEALPixCell) -> Option<HEALPixCell> {
/*if cell.is_root() {
// Root cells are in the buffer by definition
Some(*cell)
} else {*/
let mut parent_cell = cell.parent();
while !self.contains(&parent_cell) && !parent_cell.is_root() {
parent_cell = parent_cell.parent();
}
if self.contains(&parent_cell) {
Some(parent_cell)
} else {
None
}
//}
}
fn config_mut(&mut self) -> &mut HiPSConfig;
fn config(&self) -> &HiPSConfig;
}
use crate::downloader::query;
use crate::renderable::hips::HiPS::{D2, D3};
use crate::renderable::HiPS3D;
use crate::ProjectionType;
@@ -63,15 +83,10 @@ pub enum HiPS {
}
impl HiPS {
pub fn look_for_new_tiles(
&mut self,
tile_fetcher: &mut TileFetcherQueue,
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport,
) {
pub fn look_for_new_tiles(&mut self, camera: &CameraViewPort) -> Option<Vec<HEALPixCell>> {
match self {
D2(hips) => hips.look_for_new_tiles(tile_fetcher, camera, browser_features_support),
D3(hips) => hips.look_for_new_tiles(tile_fetcher, camera, browser_features_support),
D2(hips) => hips.look_for_new_tiles(camera).map(|it| it.collect()),
D3(hips) => hips.look_for_new_tiles(camera).map(|it| it.collect()),
}
}
@@ -99,10 +114,10 @@ impl HiPS {
}
#[inline]
pub fn set_root_url(&mut self, root_url: String) {
pub fn get_config_mut(&mut self) -> &mut HiPSConfig {
match self {
D2(hips) => hips.get_config_mut().set_root_url(root_url),
D3(hips) => hips.get_config_mut().set_root_url(root_url),
D2(hips) => hips.get_config_mut(),
D3(hips) => hips.get_config_mut(),
}
}
@@ -113,6 +128,30 @@ impl HiPS {
}
}
/*#[inline]
pub fn set_moc(&mut self, moc: SpaceMoc) {
match self {
D2(hips) => hips.set_moc(moc),
D3(hips) => hips.set_moc(moc),
}
}*/
#[inline]
pub fn get_tile_query(&self, cell: &HEALPixCell) -> query::Tile {
match self {
HiPS::D2(hips) => hips.get_tile_query(cell),
HiPS::D3(hips) => hips.get_tile_query(cell),
}
}
#[inline]
pub fn add_allsky(&mut self, allsky: Allsky) -> Result<(), JsValue> {
match self {
HiPS::D2(hips) => hips.add_allsky(allsky),
HiPS::D3(hips) => hips.add_allsky(allsky),
}
}
pub fn is_allsky(&self) -> bool {
self.get_config().is_allsky
}

View File

@@ -1,139 +0,0 @@
use crate::renderable::hips::d2::texture::HpxTex;
use crate::renderable::hips::d3::texture::HpxFreqTex;
use crate::time::Time;
use crate::Abort;
use crate::HEALPixCell;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[derive(Clone, Debug)]
pub struct Tile<C> {
cell: C,
time_request: Time,
}
impl<C> Tile<C> {
pub fn reset_time(&mut self) {
self.time_request = Time::now();
}
#[inline(always)]
pub fn cell(&self) -> &C {
&self.cell
}
}
impl Tile<HEALPixCell> {
pub fn is_root(&self) -> bool {
self.cell.is_root()
}
}
impl<C> PartialEq for Tile<C>
where
C: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.cell == other.cell
}
}
impl<C> Eq for Tile<C> where C: PartialEq {}
// Ordering based on the time the tile has been requested
impl<C> PartialOrd for Tile<C>
where
C: PartialEq,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<C> Ord for Tile<C>
where
C: PartialEq,
{
fn cmp(&self, other: &Self) -> Ordering {
other
.time_request
.partial_cmp(&self.time_request)
.unwrap_abort()
}
}
impl From<&HpxTex> for Tile<HEALPixCell> {
fn from(tex: &HpxTex) -> Self {
let time_request = tex.time_request;
let cell = tex.cell;
Self { cell, time_request }
}
}
use crate::healpix::cell::HEALPixFreqCell;
impl From<&HpxFreqTex> for Tile<HEALPixFreqCell> {
fn from(tex: &HpxFreqTex) -> Self {
let time_request = tex.time_request;
let cell = tex.cell.clone();
Self { cell, time_request }
}
}
pub struct TileHeap<C> {
heap: BinaryHeap<Tile<C>>,
size: usize,
}
impl<C> TileHeap<C> {
pub fn clear(&mut self) {
self.heap.clear();
}
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<C> TileHeap<C>
where
C: PartialEq,
{
pub fn with_capacity(cap: usize) -> Self {
Self {
heap: BinaryHeap::with_capacity(cap),
size: cap,
}
}
// Check if the heap is full
pub fn is_full(&self) -> bool {
self.heap.len() >= self.size
}
pub fn update_entry<T: Into<Tile<C>>>(&mut self, item: T) {
let item = item.into();
self.heap = self
.heap
.drain()
// Remove the cell
.filter(|texture_node| texture_node.cell != item.cell)
// Collect to a new binary heap that does not have cell anymore
.collect::<BinaryHeap<_>>();
self.push(item);
}
pub fn push<T: Into<Tile<C>>>(&mut self, item: T) {
let item = item.into();
self.heap.push(item);
}
pub fn pop(&mut self) -> Option<Tile<C>> {
self.heap.pop()
}
}

View File

@@ -12,35 +12,26 @@ impl<T> Deref for UV<T> {
}
}
use super::d2::texture::HpxTexture2D;
use crate::healpix::cell::HEALPixCell;
use crate::renderable::hips::HpxTile;
pub struct TileUVW(pub [Vector3<f32>; 4]);
impl TileUVW {
// The texture cell passed must be a child of texture
pub fn new(cell: &HEALPixCell, parent_cell: &Option<HEALPixCell>, w: f32) -> TileUVW {
pub fn new(cell: &HEALPixCell, texture: &HpxTexture2D) -> TileUVW {
// Index of the texture in the total set of textures
//let texture_idx = texture.idx();
let texture_idx = texture.idx();
// Row and column indexes of the tile in its texture
let (u, v, ds) = if let Some(parent) = parent_cell {
let (idx_col_in_tex, idx_row_in_tex) = cell.offset_in_parent(parent);
let (idx_col_in_tex, idx_row_in_tex) = cell.offset_in_parent(texture.cell());
let nside = (1 << (cell.depth() - parent.depth())) as f32;
let ds = 1_f32 / nside;
let nside = (1 << (cell.depth() - texture.cell().depth())) as f32;
let u = (idx_row_in_tex as f32) / nside;
let v = (idx_col_in_tex as f32) / nside;
let u = (idx_row_in_tex as f32) / nside;
let v = (idx_col_in_tex as f32) / nside;
let ds = 1_f32 / nside;
(u, v, ds)
} else {
(0.0, 0.0, 1.0)
};
//let u = 0.0;
//let v = 0.0;
//let ds = 1.0;
// let w = texture_idx as f32;
let w = texture_idx as f32;
TileUVW([
Vector3::new(u, v, w),
Vector3::new(u + ds, v, w),

View File

@@ -176,7 +176,7 @@ impl Layers {
pub fn set_hips_url(&mut self, cdid: &CreatorDid, new_url: String) -> Result<(), JsValue> {
if let Some(hips) = self.hipses.get_mut(cdid) {
// update the root_url
hips.set_root_url(new_url);
hips.get_config_mut().set_root_url(new_url.clone());
Ok(())
} else {
@@ -214,13 +214,9 @@ impl Layers {
let raytracer = &self.raytracer;
let raytracing = camera.is_raytracing(projection);
// The first layer or the background must be plot with no blending
self.gl.disable(WebGl2RenderingContext::BLEND);
// Check whether a hips to plot is allsky
// if neither are, we draw a font
// if there are, we do not draw nothing
let mut idx_start_layer = -1;
for (idx, layer) in self.layers.iter().enumerate() {
@@ -240,8 +236,6 @@ impl Layers {
}
}
let mut blending_enabled = false;
// Need to render transparency font
if idx_start_layer == -1 {
let vao = if raytracing {
@@ -265,9 +259,6 @@ impl Layers {
// The background (index -1) has been drawn, we can draw the first HiPS
idx_start_layer = 0;
self.gl.enable(WebGl2RenderingContext::BLEND);
blending_enabled = true;
}
let layers_to_render = &self.layers[(idx_start_layer as usize)..];
@@ -293,11 +284,6 @@ impl Layers {
}
}
}
if !blending_enabled {
self.gl.enable(WebGl2RenderingContext::BLEND);
blending_enabled = true;
}
}
Ok(())
@@ -452,9 +438,9 @@ impl Layers {
let hips = match &cfg.dataproduct_type {
// HiPS cube
DataproductType::Cube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
DataproductType::Cube => HiPS::D3(HiPS3D::new(cfg, gl)?),
// HiPS 3D
DataproductType::SpectralCube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
DataproductType::SpectralCube => HiPS::D3(HiPS3D::new(cfg, gl)?),
// Typical HiPS image
_ => HiPS::D2(HiPS2D::new(cfg, gl)?),
};

View File

@@ -1,4 +1,3 @@
use crate::downloader::query::CellDesc;
use crate::downloader::{query, Downloader};
use crate::time::{DeltaTime, Time};
use crate::Abort;
@@ -147,23 +146,16 @@ impl TileFetcherQueue {
fn check_in_file_list(&self, mut query: Tile) -> Result<Tile, JsValue> {
if let Some(local_hips) = self.hips_local_files.get(&query.hips_cdid) {
// TODO modify local hips file structure to support freq indices as well
match query.cell {
CellDesc::HiPS2D { cell, .. } => {
if let Some(tile) = local_hips.get_tile(&cell, *query.format.get_ext_file()) {
if let Ok(url) = web_sys::Url::create_object_url_with_blob(tile.as_ref()) {
// rewrite the url
query.url = url;
Ok(query)
} else {
Err(JsValue::from_str("could not create an url from the tile"))
}
} else {
Ok(query)
}
if let Some(tile) = local_hips.get_tile(&query.cell, *query.format.get_ext_file()) {
if let Ok(url) = web_sys::Url::create_object_url_with_blob(tile.as_ref()) {
// rewrite the url
query.url = url;
Ok(query)
} else {
Err(JsValue::from_str("could not create an url from the tile"))
}
// TODO Support for HiPS3D/Cube
_ => Ok(query),
} else {
Ok(query)
}
} else {
Ok(query)
@@ -227,10 +219,9 @@ impl TileFetcherQueue {
HiPS::D3(_) => (),
}
// FIXME: this still might be important to keep but for HiPS2D only
/*if cfg.get_min_depth_tile() == 0 {
if cfg.get_min_depth_tile() == 0 {
for tile_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
if let Ok(query) = self.check_in_file_list(hips.build_tile_query(tile_cell)) {
if let Ok(query) = self.check_in_file_list(hips.get_tile_query(tile_cell)) {
let dl = downloader.clone();
crate::utils::set_timeout(
@@ -241,6 +232,6 @@ impl TileFetcherQueue {
);
}
}
}*/
}
}
}

View File

@@ -23,27 +23,6 @@
color: white;
}
.aladin-lite-spectra-displayer .aladin-spectra-unit-selector {
position: absolute;
bottom: 3rem;
right: 0;
}
.aladin-lite-spectra-displayer .aladin-spectra-home {
position: absolute;
bottom: 5rem;
right: 0;
}
.aladin-lite-spectra-displayer .aladin-spectra-extraction {
position: absolute;
bottom: 7rem;
right: 0;
}
.aladin-lite-spectra-displayer .aladin-spectra-hips-selector {
position: absolute;
top: 0;
left: 0;
max-width: 10rem;
}
.aladin-imageCanvas {
position: absolute;
@@ -867,6 +846,7 @@
padding: 0.2rem 0;
font-size: inherit;
border-radius: 0.2rem;
box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.2);
cursor: pointer;
font-family: monospace;
box-sizing: content-box;

View File

@@ -13,21 +13,9 @@ uniform float reversed;
#include ../decode.glsl;
/////////////////////////////////////////////
/// RED sampler
vec4 uvw2c_r(vec3 uv) {
vec2 va = texture(tex, uv).ra;
va.x = transfer_func(H, va.x, min_value, max_value);
// apply reversed
va.x = mix(va.x, 1.0 - va.x, reversed);
vec4 c = colormap_f(va.x);
return apply_tonal(c);
}
/// RGBA sampler
vec4 uvw2c_rgba(vec3 uv) {
vec4 uvw2c_rgba(vec3 uv) {
vec4 c = texture(tex, uv).rgba;
c.r = transfer_func(H, c.r, min_value, max_value);
@@ -40,19 +28,6 @@ vec4 uvw2c_rgba(vec3 uv) {
return apply_tonal(c);
}
vec4 uvw2c_ra(vec3 uv) {
vec2 c = texture(tex, uv).rg;
c.r = transfer_func(H, c.r, min_value, max_value);
// apply reversed
c.r = mix(c.r, 1.0 - c.r, reversed);
vec3 color = colormap_f(c.r).rgb;
return apply_tonal(vec4(color, c.g));
}
vec4 uvw2cmap_rgba(vec3 uv) {
float v = texture(tex, uv).r;
// apply transfer f
@@ -67,6 +42,7 @@ vec4 uvw2cmap_rgba(vec3 uv) {
/////////////////////////////////////////////
/// FITS sampler
vec4 val2c_f32(float x) {
float alpha = x * scale + offset;
alpha = transfer_func(H, alpha, min_value, max_value);

View File

@@ -13,9 +13,7 @@ out vec4 out_frag_color;
uniform float opacity;
void main() {
vec3 uv = vec3(frag_uv.xyz);
uv.y = 1.0 - uv.y;
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2c_f32(uv);
out_frag_color = color;

View File

@@ -15,9 +15,7 @@ out vec4 out_frag_color;
uniform float opacity;
void main() {
vec3 uv = vec3(frag_uv.xyz);
uv.y = 1.0 - uv.y;
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2c_i16(uv);
out_frag_color = color;

View File

@@ -15,9 +15,7 @@ out vec4 out_frag_color;
uniform float opacity;
void main() {
vec3 uv = vec3(frag_uv.xyz);
uv.y = 1.0 - uv.y;
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2c_i32(uv);
out_frag_color = color;

View File

@@ -1,8 +1,6 @@
#version 300 es
precision lowp float;
precision lowp sampler3D;
precision lowp isampler3D;
precision lowp usampler3D;
uniform sampler3D tex;
@@ -14,8 +12,8 @@ uniform float opacity;
#include ../hips/color.glsl;
void main() {
vec3 uv = vec3(frag_uv.xyz);
vec4 color = uvw2c_ra(uv);
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2c_rgba(uv);
out_frag_color = color;
out_frag_color.a = opacity * out_frag_color.a;

View File

@@ -0,0 +1,20 @@
#version 300 es
precision lowp float;
precision lowp sampler3D;
uniform sampler3D tex;
in vec3 frag_uv;
out vec4 out_frag_color;
uniform float opacity;
#include ../hips/color.glsl;
void main() {
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2cmap_rgba(uv);
out_frag_color = color;
out_frag_color.a = opacity * out_frag_color.a;
}

View File

@@ -13,9 +13,7 @@ out vec4 out_frag_color;
uniform float opacity;
void main() {
vec3 uv = vec3(frag_uv.xyz);
uv.y = 1.0 - uv.y;
vec3 uv = vec3(frag_uv.xy, mod(frag_uv.z, 32.0) / 32.0);
vec4 color = uvw2c_u8(uv);
out_frag_color = color;

View File

@@ -1,25 +1,17 @@
#version 300 es
precision highp float;
precision lowp float;
out vec4 color;
in vec2 l;
in float l;
uniform vec4 u_color;
uniform float u_thickness;
uniform float u_width;
uniform float u_height;
void main() {
if (l.x > 0.05) {
// Multiply vertex color with texture color (in linear space).
// Linear color is written and blended in Framebuffer and converted to sRGB later
if (l > 0.05) {
discard;
} else {
color = u_color;
// distance from line to compute the anti-aliasing
float dist = abs((u_thickness + 2.0) * l.y);
float half_thickness = (u_thickness + 2.0) * 0.5;
color.a = color.a * (1.0 - smoothstep(half_thickness - 1.0, half_thickness, dist));
}
}

View File

@@ -11,7 +11,7 @@ uniform float u_width;
uniform float u_height;
uniform float u_thickness;
out vec2 l;
out float l;
#include ../projection/projection.glsl;
@@ -27,6 +27,7 @@ void main() {
vec2 p_b_clip = proj(p_b_w);
vec2 da = p_a_clip - p_b_clip;
l = dot(da, da);
vec2 p_a_ndc = p_a_clip / (ndc_to_clip * czf);
vec2 p_b_ndc = p_b_clip / (ndc_to_clip * czf);
@@ -36,12 +37,6 @@ void main() {
vec2 y_b = normalize(vec2(-x_b.y, x_b.x));
float ndc2pix = 2.0 / u_width;
vec2 p_ndc_x = x_b * vertex.x;
vec2 p_ndc_y = (u_thickness + 2.0) * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
vec2 p_ndc = p_a_ndc + p_ndc_x + p_ndc_y;
vec2 p_ndc = p_a_ndc + x_b * vertex.x + u_thickness * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
gl_Position = vec4(p_ndc, 0.f, 1.f);
l = vec2(dot(da, da), vertex.y);
}

View File

@@ -4,7 +4,7 @@ layout (location = 0) in vec2 p_a;
layout (location = 1) in vec2 p_b;
layout (location = 2) in vec2 vertex;
out vec2 l;
out float l;
uniform float u_width;
uniform float u_height;
@@ -16,7 +16,6 @@ void main() {
float ndc2pix = 2.0 / u_width;
vec2 p = p_a + x_b * vertex.x + (u_thickness + 2.0) * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
vec2 p = p_a + x_b * vertex.x + u_thickness * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
gl_Position = vec4(p, 0.f, 1.f);
l = vec2(0.0, vertex.y);
}

View File

@@ -53,6 +53,15 @@ float one_minus_z_neg(vec3 p) {
int ij2z(int i, int j) {
int i4 = i | (j << 2);
/*int j1 = (i1 ^ (i1 >> 8)) & 0x0000FF00;
int i2 = i1 ^ j1 ^ (j1 << 8);
int j2 = (i2 ^ (i2 >> 4)) & 0x00F000F0;
int i3 = i2 ^ j2 ^ (j2 << 4);
int j3 = (i3 ^ (i3 >> 2)) & 0x0C0C0C0C;
int i4 = i3 ^ j3 ^ (j3 << 2);*/
int j4 = (i4 ^ (i4 >> 1)) & 0x22222222;
int i5 = i4 ^ j4 ^ (j4 << 1);

View File

@@ -26,27 +26,27 @@ uniform int u_proj;
vec2 proj(vec3 p) {
if (u_proj == 0) {
// TAN, Gnomonic projection
/* TAN, Gnomonic projection */
return w2c_tan(p);
} else if (u_proj == 1) {
// STG, Stereographic projection
/* STG, Stereographic projection */
return w2c_stg(p);
} else if (u_proj == 2) {
// SIN, Orthographic
/* SIN, Orthographic */
return w2c_sin(p);
} else if (u_proj == 3) {
// ZEA, Equal-area
/* ZEA, Equal-area */
return w2c_zea(p);
} else if (u_proj == 4) {
// Pseudo-cylindrical projections
// AIT, Aitoff
/* AIT, Aitoff */
return w2c_ait(p);
} else if (u_proj == 5) {
// MOL, Mollweide
// MOL, Mollweide */
return w2c_mol(p);
} else {
// Cylindrical projections
// MER, Mercator
// MER, Mercator */
return w2c_mer(p);
}
}

View File

@@ -573,10 +573,6 @@ A.catalogFromURL = function (url, options, successCallback, errorCallback, usePr
const processVOTable = function (table) {
let {sources, fields} = table;
c.setFields(fields);
if (fields.s_region) {
// for ObsCore tables, show also the (ra, dec) as a source
c.onlyFootprints = false;
}
c.addSources(sources);
const s_regionFieldFound = Array.from(Object.keys(fields)).find((f) => f.toLowerCase() === 's_region');

View File

@@ -107,21 +107,21 @@ import { Polyline } from "./shapes/Polyline";
* @property {boolean} [showCooGridControl=false] - Whether to show the coordinate grid control toolbar.
* CSS class for that button is `aladin-grid-control`
* @property {boolean} [showSettingsControl=false] - Whether to show the settings control toolbar.
* CSS class for that button is `aladin-settings-control`
* CSS class for that button is `aladin-settings-control`
* @property {boolean} [showColorPickerControl=false] - Whether to show the color picker tool.
* CSS class for that button is `aladin-colorPicker-control`
* @property {boolean} [showShareControl=false] - Whether to show the share control toolbar.
* CSS class for that button is `aladin-share-control`
* CSS class for that button is `aladin-share-control`
* @property {boolean} [showStatusBar=true] - Whether to show the status bar. Enabled by default.
* CSS class for that button is `aladin-status-bar`
* CSS class for that button is `aladin-status-bar`
* @property {boolean} [showFrame=true] - Whether to show the viewport frame.
* CSS class for that button is `aladin-cooFrame`
* CSS class for that button is `aladin-cooFrame`
* @property {boolean} [showFov=true] - Whether to show the field of view indicator.
* CSS class for that button is `aladin-fov`
* CSS class for that button is `aladin-fov`
* @property {boolean} [showCooLocation=true] - Whether to show the coordinate location indicator.
* CSS class for that button is `aladin-location`
* CSS class for that button is `aladin-location`
* @property {boolean} [showProjectionControl=true] - Whether to show the projection control toolbar.
* CSS class for that button is `aladin-projection-control`
* CSS class for that button is `aladin-projection-control`
* @property {boolean} [showContextMenu=false] - Whether to show the context menu.
* @property {boolean} [showReticle=true] - Whether to show the reticle.
* @property {boolean} [showCatalog=true] - Whether to show the catalog.
@@ -131,20 +131,20 @@ import { Polyline } from "./shapes/Polyline";
* @property {boolean} [fullScreen=false] - Whether to start in full-screen mode.
* @property {string} [reticleColor="rgb(178, 50, 178)"] - Color of the reticle in RGB format.
* @property {number} [reticleSize=22] - Size of the reticle.
*
* @property {string} [gridColor="rgb(178, 50, 178)"] - Color of the grid in RGB format.
*
* @property {string} [gridColor="rgb(178, 50, 178)"] - Color of the grid in RGB format.
* Is overshadowed by gridOptions.color if defined.
* @property {number} [gridOpacity=0.8] - Opacity of the grid (0 to 1).
* @property {number} [gridOpacity=0.8] - Opacity of the grid (0 to 1).
* Is overshadowed by gridOptions.opacity if defined.
* @property {Object} [gridOptions] - More options for the grid.
* @property {string} [gridOptions.color="rgb(178, 50, 178)"] - Color of the grid. Can be specified as a named color
* @property {string} [gridOptions.color="rgb(178, 50, 178)"] - Color of the grid. Can be specified as a named color
* (see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/named-color| named colors}),
* as rgb (ex: "rgb(178, 50, 178)"), or as a hex color (ex: "#86D6AE").
* as rgb (ex: "rgb(178, 50, 178)"), or as a hex color (ex: "#86D6AE").
* @property {number} [gridOptions.thickness=2] - The thickness of the grid, in pixels.
* @property {number} [gridOptions.opacity=0.8] - Opacity of the grid and labels. It is comprised between 0 and 1.
* @property {boolean} [gridOptions.showLabels=true] - Whether the grid has labels.
* @property {number} [gridOptions.labelSize=15] - The font size of the labels.
*
*
* @property {string} [projection="SIN"] - Projection type. Can be 'SIN' for orthographic, 'MOL' for mollweide, 'AIT' for hammer-aitoff, 'ZEA' for zenital equal-area or 'MER' for mercator
* @property {boolean} [longitudeReversed=false] - Longitude reverse axis flag. Set to true to reverse the longitude axis. This is especially needed for planetary survey visualization. Default is set to false.
* @property {boolean} [log=true] - Whether to log events.
@@ -155,7 +155,7 @@ import { Polyline } from "./shapes/Polyline";
* @property {Object} [selector] - More options for the the selector.
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
* @property {number} [selector.lineWidth=2] - Width of the selector line.
*
*
* @example
* let aladin = A.aladin({
target: 'galactic center',
@@ -254,7 +254,7 @@ import { Polyline } from "./shapes/Polyline";
/**
* @typedef {('select'|'objectsSelected'|'objectClicked'|'objectHovered'|'objectHoveredStop'|'footprintClicked'|'footprintHovered'|'positionChanged'|'zoomChanged'|'rotationChanged'|'click'|'rightClickMove'|'mouseMove'|'wheelTriggered'|'fullScreenToggled'|'cooFrameChanged'|'resizeChanged'|'projectionChanged'|'layerChanged')} EventListener
*
*
* <ul>
* <li>'positionChanged' is triggered when the view position has been changed. It gives the user the new center position of the view in ICRS frame. See {@link positionChangedParam}</li>
* <li>'select' is <b>deprecated</b>, please use 'objectsSelected' instead.</li>
@@ -285,9 +285,6 @@ export let Aladin = (function () {
this.callbacksByEventName = {}; // we store the callback functions (on 'zoomChanged', 'positionChanged', ...) here
this.hipsCache = new HiPSCache();
this.customShareURLFn = null;
// check that aladinDiv exists, stop immediately otherwise
if (!aladinDiv) {
console.error(
@@ -383,7 +380,7 @@ export let Aladin = (function () {
this.gotoObject(options.target, undefined);
if (options.log) {
var params = requestedOptions;
var params = options;
params["version"] = Aladin.VERSION;
Logger.log("startup", params);
}
@@ -393,7 +390,7 @@ export let Aladin = (function () {
this.createCatalogFromVOTable(options.catalogUrls[k]);
}
}
// Format the hipslist given by the user before storing it in the aladin objec
this.hipsFavorites = [];
let hipsList = [].concat(options.hipsList);
@@ -739,7 +736,7 @@ export let Aladin = (function () {
gridOptions: {
enabled: false,
showLabels: true,
thickness: 1,
thickness: 2,
labelSize: 15,
},
projection: "SIN",
@@ -771,7 +768,7 @@ export let Aladin = (function () {
ui.toggle();
}
})
if (realFullscreen) {
// go to "real" full screen mode
if (self.isInFullscreen) {
@@ -898,14 +895,14 @@ export let Aladin = (function () {
/**
* Read pixels inside the Aladin Lite canvas
*
*
* @description
* Returns the rgba pixels composing the current view.
* Please keep in mind that this method returns the actual colors that you see in the screen, it is not intended to return values coming from the progenitors.
* For a knowing exactly the values of a specific HiPS (e.g. the real FITS values from HiPS FITS tiles) please use the method {@link HiPS#readPixel}.
*
*
* @memberof Aladin
* @param {PixelProber[]|RectProber[]} [prober] - A prob object. Can be unique prober or a list of it. By default, the center of the view is probed, i.e. the pixel under the reticle.
* @param {PixelProber[]|RectProber[]} [prober] - A prob object. Can be unique prober or a list of it. By default, the center of the view is probed, i.e. the pixel under the reticle.
* @returns {ImageData} A {@link https://developer.mozilla.org/fr/docs/Web/API/ImageData| ImageData} JS object coming from the canvas probing. Its `data` field stores the byte pixel array containing a list of 4 bytes RGBA values.
*/
Aladin.prototype.readCanvas = function (prober) {
@@ -1016,6 +1013,8 @@ export let Aladin = (function () {
let aladinBorderColor = Color.getLabelColorForBackground(`rgb(${aladinColor.r}, ${aladinColor.g}, ${aladinColor.b})`);
this.aladinDiv.style.setProperty('--aladin-color-border', aladinBorderColor)
console.log(aladinBorderColor)
};
/**
@@ -1582,7 +1581,7 @@ export let Aladin = (function () {
for (var ui of ui) {
this.ui.push(ui);
ui.attachTo(this.aladinDiv);
// as the ui is pushed to the dom, setting position may need the aladin instance to work
// so we recompute it
if (ui.options) {
@@ -1701,10 +1700,10 @@ export let Aladin = (function () {
/**
* Remove a HiPS from the list of favorites.
*
* This send a event of type
*
* This send a event of type
* FAVORITE_HIPS_LIST_UPDATED which can be listened to
*
*
* @throws A warning when the asset is currently present in the view
*
* @memberof Aladin
@@ -1731,7 +1730,7 @@ export let Aladin = (function () {
}
})
// a hips matches
// a hips matches
if (idx >= 0) {
this.hipsFavorites.splice(idx, 1);
// Send a change of favorites for the UI selector to adapt their optional list
@@ -1741,11 +1740,11 @@ export let Aladin = (function () {
/**
* Add a HiPS to the list of favorites.
*
* If already present it will not add it again. This send a event of type
*
* If already present it will not add it again. This send a event of type
* FAVORITE_HIPS_LIST_UPDATED which can be listened to. Once added, the favorite list
* will be sorted by the name of the hips.
*
*
* @memberof Aladin
* @param {HiPS} hips - The HiPS to add to the favorites
*/
@@ -1872,7 +1871,7 @@ export let Aladin = (function () {
Aladin.prototype.newImageSurvey = function (id, options) {
// a wrapper on createImageSurvey that aggregates all params in an options object
return this.createImageSurvey(
id,
id,
options && options.name,
id,
options && options.cooFrame,
@@ -2049,7 +2048,7 @@ export let Aladin = (function () {
if (cachedOptions) {
imageLayer = A.HiPS(idOrUrl, cachedOptions);
} else {
// 2/ Not in the cache, then we create the hips from this url/id and
// 2/ Not in the cache, then we create the hips from this url/id and
// go to the case 3
imageLayer = A.HiPS(idOrUrl);
@@ -2111,12 +2110,12 @@ export let Aladin = (function () {
};
/**
* Get the view center to north pole angle in degrees. This is equivalent to getting the 3rd Euler angle
*
* @memberof Aladin
*
*
* @returns {number} - Angle between the position center and the north pole
*/
Aladin.prototype.getRotation = function () {
@@ -2125,7 +2124,7 @@ export let Aladin = (function () {
/**
* Set the view center rotation in degrees
*
*
* @deprecated Use Aladin.prototype.setRotation instead
*
* @memberof Aladin
@@ -2138,9 +2137,9 @@ export let Aladin = (function () {
* Get the view center to north pole angle in degrees. This is equivalent to getting the 3rd Euler angle
*
* @memberof Aladin
*
*
* @deprecated
*
*
* @returns {number} - Angle between the position center and the north pole
*/
Aladin.prototype.getViewCenter2NorthPoleAngle = Aladin.prototype.getRotation;
@@ -2306,7 +2305,7 @@ export let Aladin = (function () {
/**
* Select specific objects in the view
*
*
* @memberof Aladin
* @param {?Array.<Source, Footprint, Circle, Ellipse, Polyline, Vector>} objects - If null is passed then nothing will be selected and sources already selected will be deselected
*/
@@ -2648,7 +2647,7 @@ export let Aladin = (function () {
// Reverse the Eq 9 from the WCS II paper from Mark Calabretta to obtain LONPOLE
// function of CRVAL2 and native coordinates of the fiducial ref point, i.e. (phi_0, theta_0) = (0, 0)
// for cylindrical projections
// for cylindrical projections
WCS.LONPOLE = Math.asin(Math.sin(dLon * toRad) * Math.cos(WCS.CRVAL2 * toRad)) * toDeg;
if (WCS.CRVAL2 < 0) {
@@ -2728,7 +2727,7 @@ export let Aladin = (function () {
if (frame instanceof string) {
frame = CooFrameEnum.fromString(frame, CooFrameEnum.ICRS);
}
if (frame.label == CooFrameEnum.SYSTEMS.GAL) {
frame = Aladin.wasmLibs.core.CooSystem.GAL;
}
@@ -2914,18 +2913,14 @@ export let Aladin = (function () {
* return a URL allowing to share the current view
*/
Aladin.prototype.getShareURL = function () {
// do we have a custom share URL function set?
if (this.customShareURLFn) {
return this.customShareURLFn();
}
const radec = this.getRaDec();
const coo = new Coo();
var radec = this.getRaDec();
var coo = new Coo();
coo.prec = 7;
coo.lon = radec[0];
coo.lat = radec[1];
return Aladin.URL_PREVIEWER +
return (
Aladin.URL_PREVIEWER +
"?target=" +
encodeURIComponent(coo.format("s")) +
"&fov=" +
@@ -2933,25 +2928,10 @@ export let Aladin = (function () {
"&survey=" +
encodeURIComponent(
this.getBaseImageLayer().id || this.getBaseImageLayer().rootUrl
);
)
);
};
/**
* Customize share URL creation
* @memberof Aladin
* @param {Function} [customShareURLFn] - The function that will be called when a user clicks on "Get view URL", to share with collaborators
*
* @example
aladin.customizeShareURLFunction(() => {return 'https://sky.esa.int/esasky/?target=' + aladin.getRaDec()[0] + '%20' + aladin.getRaDec()[1] + '&fov=' + aladin.getFoV()[0]})
*/
Aladin.prototype.customizeShareURLFunction = function(customShareURLFn) {
if (! typeof customShareURLFn === 'function') {
this.customShareURLFn = null;
}
this.customShareURLFn = customShareURLFn;
}
// @API
/*
* return, as a string, the HTML embed code
@@ -3111,13 +3091,12 @@ aladin.displayFITS(
options.body = JSON.stringify(params);
}
return fetch(url, options).then((response) => response.json()).catch(e => console.error(e));
return fetch(url, options).then((response) => response.json());
};
const get = (url, params) => request(url, params, "GET");
get("https://alasky.unistra.fr/cgi/fits2HiPS", data).then(
async (response) => {
console.log(response, data)
if (response.status != "success") {
console.error("An error occured: " + response.message);
if (errorCallback) {
@@ -3147,7 +3126,7 @@ aladin.displayFITS(
);
}
if (executeDefaultSuccessAction === true) {
self.gotoRaDec(meta.ra, meta.dec);
self.wasm.setCenter(meta.ra, meta.dec);
self.setFoV(meta.fov);
}
@@ -3157,7 +3136,7 @@ aladin.displayFITS(
// This has to be fixed in the backend but a fast fix is just to wait
// before setting a new image survey
}
).catch((e) => console.error(e));
);
};
Aladin.prototype.displayPNG = Aladin.prototype.displayJPG;
@@ -3166,7 +3145,7 @@ aladin.displayFITS(
* Add a custom colormap from a list of colors
*
* @memberof Aladin
*
*
* @returns - The list of all the colormap labels
*/
Aladin.prototype.getListOfColormaps = function() {
@@ -3179,9 +3158,9 @@ aladin.displayFITS(
* @memberof Aladin
* @param {string} label - The label of the colormap
* @param {string[]} colors - A list string colors
*
*
* @example
*
*
* aladin.addColormap('mycmap', ["lightblue", "red", "violet", "#ff00aaff"])
*/
Aladin.prototype.addColormap = function(label, colors) {

View File

@@ -36,13 +36,13 @@ import A from "./A.js";
import { Footprint } from "./Footprint.js";
/**
* Represents options for configuring a catalog.
*
* @typedef {Object} CatalogOptions
* Represents options for configuring a catalog.
*
* @typedef {Object} CatalogOptions
* @property {string} url - The URL of the catalog.
* @property {string} [name="catalog"] - The name of the catalog.
* @property {string|Function} [color] - The color associated with the catalog. A function can be given similar to `shape`.
* @property {number|Function} [sourceSize=8] - The size of the sources in the catalog. A function can be given similar to `shape`.
* @property {string} [color] - The color associated with the catalog.
* @property {number} [sourceSize=8] - The size of the sources in the catalog.
* @property {string|Function|Image|HTMLCanvasElement|HTMLImageElement} [shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
If a function is given, user can return Image, HTMLImageCanvas, HTMLImageElement or a string being in ["square", "circle", "plus", "cross", "rhomb", "triangle"]. This allows to define different shape for a specific catalog source.
* @property {number} [limit] - The maximum number of sources to display.
@@ -57,7 +57,6 @@ If a function is given, user can return Image, HTMLImageCanvas, HTMLImageElement
* @property {string} [labelColumn] - The name of the column to be used for the label.
* @property {string} [labelColor=color] - The color of the source labels.
* @property {string} [labelFont="10px sans-serif"] - The font for the source labels.
* @property {boolean} [onlyFootprints=true] - When shapes/footprints are associated to a source (through a shape function given), decide wheter to show the point source as well. Point source is hidden by default
*/
export let Catalog = (function () {
@@ -96,10 +95,6 @@ export let Catalog = (function () {
this.markerSize = options.sourceSize || 12;
this.selectSize = this.sourceSize;
this.shape = options.shape || "square";
if (typeof this.shape === "function") {
this.shapeFn = this.shape;
this.shape = "custom";
}
this.maxNbSources = options.limit || undefined;
this.onClick = options.onClick || undefined;
this.readOnly = options.readOnly || false;
@@ -110,14 +105,10 @@ export let Catalog = (function () {
// allows for filtering of sources
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
this.selectionColor = options.selectionColor || "#00ff00";
this.hoverColor = options.hoverColor || undefined;
// when footprints are associated to source, do we need to draw the point source as well ?
this.onlyFootprints = options.onlyFootprints ?? true;
this.hoverColor = options.hoverColor || this.color;
this.displayLabel = options.displayLabel || false;
this.labelColor = options.labelColor || undefined;
this.labelColor = options.labelColor || this.color;
this.labelFont = options.labelFont || "10px sans-serif";
if (this.displayLabel) {
this.labelColumn = options.labelColumn;
@@ -135,6 +126,7 @@ export let Catalog = (function () {
this.sources = [];
this.ra = [];
this.dec = [];
this.footprints = [];
// create this.cacheCanvas
// cacheCanvas permet de ne créer le path de la source qu'une fois, et de le réutiliser (cf. http://simonsarris.com/blog/427-increasing-performance-by-caching-paths-on-canvas)
@@ -471,6 +463,7 @@ export let Catalog = (function () {
if (successCallback) {
successCallback({
sources,
//footprints,
fields,
});
}
@@ -482,18 +475,6 @@ export let Catalog = (function () {
);
};
Catalog.prototype.getCacheCanvas = function(shape, color, size) {
const key = `${shape}_${size}_${color}`;
if (!(key in this.cacheCanvas)) {
this.cacheCanvas[key] = Catalog.createShape(
shape,
color,
size
)
}
return this.cacheCanvas[key];
};
/**
* Set the shape of the sources
*
@@ -512,18 +493,16 @@ export let Catalog = (function () {
options = options || {};
this.color = options.color || this.color || Color.getNextColor();
this.selectionColor = options.selectionColor || this.selectionColor || Color.getNextColor();
this.hoverColor = options.hoverColor || this.hoverColor || undefined;
this.hoverColor = options.hoverColor || this.hoverColor || this.color;
this.sourceSize = options.sourceSize || this.sourceSize || 6;
this.shape = options.shape || this.shape || "square";
if (typeof this.shape === "function") {
this.shapeFn = this.shape;
this.shape = "custom"
}
this.onClick = options.onClick || this.onClick;
if (this.shapeFn) {
this._shapeIsFunction = false; // if true, the shape is a function drawing on the canvas
if (typeof this.shape === "function") {
this._shapeIsFunction = true;
// A shape function that operates on the canvas gives the ctx and fov params
this._shapeOperatesOnCtx = this.shapeFn.length > 1;
this._shapeOperatesOnCtx = this.shape.length > 1;
// do not need to compute any canvas
// there is a possibility that the user gives a function returning shape objects such as
@@ -538,21 +517,31 @@ export let Catalog = (function () {
this._shapeIsImageOrCanvas = true;
}
if (typeof this.color === "function") {
this.colorFn = this.color;
this.color = "custom"
}
if (typeof this.sourceSize === "function") {
this.sourceSizeFn = this.sourceSize;
this.sourceSize = "custom";
} else {
this.selectSize = this.sourceSize + 2;
}
this.selectSize = this.sourceSize + 2;
// Create all the variant shaped canvas
this.cacheCanvas = {}
this.computeFootprints(this.sources);
this.cacheHoverCanvas = {}
this.cacheSelectCanvas = {}
for (var shape of Catalog.shapes) {
this.cacheCanvas[shape] = Catalog.createShape(
shape,
this.color,
this.sourceSize
)
this.cacheHoverCanvas[shape] = Catalog.createShape(
shape,
this.hoverColor,
this.selectSize
);
this.cacheSelectCanvas[shape] = Catalog.createShape(
shape,
this.selectionColor,
this.selectSize
);
}
this.reportChange();
};
@@ -595,88 +584,61 @@ export let Catalog = (function () {
this.dec.push(sources[k].dec);
}
this.computeFootprints(this.sources);
this.recomputeFootprints = true;
this.reportChange();
};
Catalog.prototype.computeFootprints = function (sources) {
if (!sources)
return;
let footprints = [];
if ((this.shapeFn || this.colorFn || this.sourceSizeFn) && !this._shapeOperatesOnCtx) {
if (this._shapeIsFunction && !this._shapeOperatesOnCtx) {
for (let source of sources) {
if (this.shapeFn) {
try {
let shapes = this.shapeFn(source);
if (shapes) {
shapes = [].concat(shapes);
try {
let shapes = this.shape(source);
if (shapes) {
shapes = [].concat(shapes);
// Result of the func is an image/canvas
if (shapes.length == 1 && (shapes[0] instanceof Image || shapes[0] instanceof HTMLCanvasElement)) {
source.setImage(shapes[0]);
// Result of the func is shape label ('cross', 'plus', ...)
} else if (shapes.length == 1 && typeof shapes[0] === "string") {
// If not found, select the square canvas
let shape = shapes[0] || "square";
source.setShape(shape)
// Result of the shape is a set of shapes or a footprint
} else {
let color = (this.colorFn && this.colorFn(source)) || this.color;
let hoverColor = this.hoverColor || color;
for (var shape of shapes) {
// Set the same color of the shape than the catalog.
// FIXME: the color/shape could be a parameter at the source level, allowing the user single catalogs handling different shapes
shape.setColor(color)
shape.setSelectionColor(this.selectionColor);
shape.setHoverColor(hoverColor);
}
let footprint;
if (shapes.length == 1 && shapes[0] instanceof Footprint) {
footprint = shapes[0];
} else {
footprint = new Footprint(shapes);
}
source.setFootprint(footprint)
// Result of the func is an image/canvas
if (shapes.length == 1 && (shapes[0] instanceof Image || shapes[0] instanceof HTMLCanvasElement)) {
source.setImage(shapes[0]);
// Result of the func is shape label ('cross', 'plus', ...)
} else if (shapes.length == 1 && typeof shapes[0] === "string") {
// If not found, select the square canvas
let shape = shapes[0] || "square";
source.setShape(shape)
// Result of the shape is a set of shapes or a footprint
} else {
for (var shape of shapes) {
// Set the same color of the shape than the catalog.
// FIXME: the color/shape could be a parameter at the source level, allowing the user single catalogs handling different shapes
shape.setColor(this.color)
shape.setSelectionColor(this.selectionColor);
shape.setHoverColor(this.hoverColor);
}
}
} catch (e) {
// do not create the footprint
console.warn("Shape computation error");
continue;
}
}
if (this.colorFn) {
try {
let color = this.colorFn(source);
if (color) {
source.setColor(color);
}
} catch (e) {
// do not create the footprint
console.warn("Source color computation error");
continue;
}
}
let footprint;
if (shapes.length == 1 && shapes[0] instanceof Footprint) {
footprint = shapes[0];
} else {
footprint = new Footprint(shapes, source);
}
if (this.sourceSizeFn) {
try {
let size = this.sourceSizeFn(source);
if (size) {
source.setSize(size);
footprint.setCatalog(this);
// store the footprints
footprints.push(footprint);
}
} catch (e) {
// do not create the footprint
console.warn("Source size computation error");
continue;
}
} catch (e) {
// do not create the footprint
console.warn("Return of shape function could not be interpreted as a footprint");
continue;
}
}
}
return footprints;
};
Catalog.prototype.setFields = function (fields) {
@@ -748,6 +710,17 @@ export let Catalog = (function () {
return this.sources;
};
/**
* Get all the footprints
*
* @memberof Catalog
*
* @returns {Footprint[]} - an array of all the footprints in the catalog object
*/
Catalog.prototype.getFootprints = function () {
return this.footprints;
};
/**
* Select all the source catalog
*
@@ -828,32 +801,6 @@ export let Catalog = (function () {
this.updateShape();
};
/**
* Select sources of the catalog matching a given callback
*
* @memberof Catalog
*
* @param {Function} filter - A filter callback to select sources of a catalog.
*/
Catalog.prototype.select = function(filter) {
let selection = [];
if (typeof filter === "function") {
for ( var s of this.sources ) {
if (filter(s)) {
selection.push(s)
}
}
this.view.selectObjects([selection]);
}
if (this.view && this.view.aladin.callbacksByEventName) {
var callback = this.view.aladin.callbacksByEventName['objectsSelected'] || this.view.aladin.callbacksByEventName['select'];
if (callback) {
callback([selection]);
}
}
}
/**
* Set the color of hovered sources
*
@@ -922,7 +869,7 @@ export let Catalog = (function () {
this.ra.splice(idx, 1);
this.dec.splice(idx, 1);
this.computeFootprints(this.sources);
this.recomputeFootprints = true;
this.reportChange();
};
@@ -937,6 +884,7 @@ export let Catalog = (function () {
this.sources = [];
this.ra = [];
this.dec = [];
this.footprints = [];
this.reportChange();
};
@@ -949,21 +897,24 @@ export let Catalog = (function () {
// tracé simple
ctx.strokeStyle = this.color;
if (this.shapeFn) {
// Draw the footprints first
this.drawFootprints(ctx);
if (this._shapeIsFunction) {
ctx.save();
}
const drawnSources = this.drawSources(ctx, width, height);
if (this.shapeFn) {
if (this._shapeIsFunction) {
ctx.restore();
}
// Draw labels
if (this.displayLabel) {
ctx.fillStyle = this.labelColor;
ctx.font = this.labelFont;
drawnSources.forEach((s) => {
ctx.fillStyle = this.labelColor || s.color || this.color;
this.drawSourceLabel(s, ctx);
});
}
@@ -971,7 +922,6 @@ export let Catalog = (function () {
Catalog.prototype.drawSources = function (ctx, width, height) {
let inside = [];
let self = this;
if (!this.sources) {
return;
@@ -986,7 +936,8 @@ export let Catalog = (function () {
return self.drawSource(s, ctx, width, height)
};
this.sources.forEach((s, idx) => {
let self = this;
this.sources.forEach(function (s, idx) {
let drawn = false;
if (xy[2 * idx] && xy[2 * idx + 1]) {
@@ -1031,18 +982,13 @@ export let Catalog = (function () {
return false;
}
if (s.isFootprint()) {
s.footprint.draw(ctx, this.view)
s.tooSmallFootprint = s.footprint.isTooSmall();
if (!s.tooSmallFootprint && this.onlyFootprints) {
return true;
}
if (s.hasFootprint && !s.tooSmallFootprint) {
return false;
}
if (s.x <= width && s.x >= 0 && s.y <= height && s.y >= 0) {
if (this._shapeOperatesOnCtx) {
this.shapeFn(s, ctx, this.view.getViewParams());
this.shape(s, ctx, this.view.getViewParams());
} else if (this._shapeIsImageOrCanvas) {
// Global catalog shape set as an Image, an HTMLCanvasElement or HTMLImageElement
let canvas = this.shape;
@@ -1065,34 +1011,23 @@ export let Catalog = (function () {
s.y - this.sourceSize / 2
);
} else if (s.isSelected) {
let selectSize = (s.size || this.sourceSize) + 2;
let shape = s.shape || this.shape || "square"
let color = this.selectionColor;
let cacheSelectCanvas = this.cacheSelectCanvas[s.shape || this.shape] || this.cacheSelectCanvas["square"];
let cacheSelectedCanvas = this.getCacheCanvas(shape, color, selectSize)
ctx.drawImage(
cacheSelectedCanvas,
s.x - cacheSelectedCanvas.width / 2,
s.y - cacheSelectedCanvas.height / 2
cacheSelectCanvas,
s.x - this.selectSize / 2,
s.y - this.selectSize / 2
);
} else if (s.isHovered) {
let selectSize = (s.size || this.sourceSize) + 2;
let shape = s.shape || this.shape || "square"
let color = this.hoverColor || s.color || this.color;
let cacheHoverCanvas = this.cacheHoverCanvas[s.shape || this.shape] || this.cacheHoverCanvas["square"];
let cacheHoverCanvas = this.getCacheCanvas(shape, color, selectSize)
ctx.drawImage(
cacheHoverCanvas,
s.x - cacheHoverCanvas.width / 2,
s.y - cacheHoverCanvas.height / 2
s.x - this.selectSize / 2,
s.y - this.selectSize / 2
);
} else {
let shape = s.shape || this.shape || "square"
let size = s.size || this.sourceSize;
let color = s.color || this.color;
let cacheCanvas = this.getCacheCanvas(shape, color, size)
let cacheCanvas = this.cacheCanvas[s.shape || this.shape] || this.cacheCanvas["square"];
ctx.drawImage(
cacheCanvas,
s.x - cacheCanvas.width / 2,
@@ -1119,6 +1054,32 @@ export let Catalog = (function () {
ctx.fillText(label, s.x, s.y);
};
Catalog.prototype.drawFootprints = function (ctx) {
if (this.recomputeFootprints) {
this.footprints = this.computeFootprints(this.sources);
this.recomputeFootprints = false;
}
var f;
for (let k = 0; k < this.footprints.length; k++) {
f = this.footprints[k];
if (this.filterFn && f.source) {
if(!this.filterFn(f.source)) {
f.hide()
} else {
f.show()
f.draw(ctx, this.view);
f.source.tooSmallFootprint = f.isTooSmall();
}
} else {
f.draw(ctx, this.view);
f.source.tooSmallFootprint = f.isTooSmall();
}
}
};
// callback function to be called when the status of one of the sources has changed
Catalog.prototype.reportChange = function () {
this.view && this.view.requestRedraw();
@@ -1134,6 +1095,10 @@ export let Catalog = (function () {
return;
}
this.isShowing = true;
// Dispatch to the footprints
if (this.footprints) {
this.footprints.forEach((f) => f.show());
}
this.reportChange();
};
@@ -1156,6 +1121,10 @@ export let Catalog = (function () {
) {
this.view.popup.hide();
}
// Dispatch to the footprints
if (this.footprints) {
this.footprints.forEach((f) => f.hide());
}
this.reportChange();
};

View File

@@ -45,6 +45,7 @@ export let DefaultActionsForContextMenu = (function () {
const a = aladinInstance;
const selectObjects = (selection) => {
console.log(selection)
a.view.selectObjects(selection);
};
return [

View File

@@ -37,15 +37,14 @@ import { Utils } from './Utils';
export let Footprint= (function() {
// constructor
//let Footprint = function(shapes, source) {
let Footprint = function(shapes) {
let Footprint = function(shapes, source) {
// All graphics overlay have an id
this.id = 'footprint-' + Utils.uuidv4();
/*this.source = source;
this.source = source;
if (this.source) {
this.source.hasFootprint = true;
}*/
}
this.shapes = [].concat(shapes);
@@ -55,7 +54,7 @@ export let Footprint= (function() {
this.overlay = null;
};
/*Footprint.prototype.setSource = function(source) {
Footprint.prototype.setSource = function(source) {
if (this.source) {
this.source.hasFootprint = false;
}
@@ -65,14 +64,23 @@ export let Footprint= (function() {
if (this.source) {
this.source.hasFootprint = true;
}
}*/
}
/*Footprint.prototype.setCatalog = function(catalog) {
Footprint.prototype.setCatalog = function(catalog) {
if (this.source) {
this.source.setCatalog(catalog);
/*for (var s of this.shapes) {
if (!s.color) {
s.setColor(catalog.color);
}
// Selection and hover color are catalog options
s.setSelectionColor(catalog.selectionColor);
s.setHoverColor(catalog.hoverColor);
}*/
}
};*/
};
Footprint.prototype.show = function() {
if (this.isShowing) {
@@ -113,10 +121,10 @@ export let Footprint= (function() {
return;
}
/*let catalog = this.getCatalog();
let catalog = this.getCatalog();
if (catalog) {
catalog.view && catalog.view.requestRedraw();
}*/
}
};
Footprint.prototype.unhover = function() {
@@ -130,6 +138,11 @@ export let Footprint= (function() {
if (this.overlay) {
this.overlay.reportChange();
}
let catalog = this.getCatalog();
if (catalog) {
catalog.view && catalog.view.requestRedraw();
}
};
Footprint.prototype.getLineWidth = function() {
@@ -177,7 +190,7 @@ export let Footprint= (function() {
return hasBeenDrawn;
};
/*Footprint.prototype.actionClicked = function() {
Footprint.prototype.actionClicked = function() {
if (this.source) {
this.source.actionClicked(this);
}
@@ -189,7 +202,7 @@ export let Footprint= (function() {
}
this.shapes.forEach((shape) => shape.deselect())
};*/
};
// If one shape is is stroke then the whole footprint is
Footprint.prototype.isInStroke = function(ctx, view, x, y) {
@@ -200,16 +213,16 @@ export let Footprint= (function() {
return this.shapes.every((shape) => shape.isTooSmall);
};
/*Footprint.prototype.getCatalog = function() {
Footprint.prototype.getCatalog = function() {
return this.source && this.source.catalog;
};*/
};
Footprint.prototype.setOverlay = function(overlay) {
this.overlay = overlay;
};
Footprint.prototype.intersectsBBox = function(x, y, w, h, view) {
/*if(this.source) {
if(this.source) {
let s = this.source;
if (!s.isShowing) {
@@ -237,8 +250,8 @@ export let Footprint= (function() {
if (c.x >= x && c.x <= x + w && c.y >= y && c.y <= y + h) {
return true;
}
}*/
return this.shapes.some((shape) => shape.intersectsBBox(x, y, w, h, view));
}
return false;
};
return Footprint;

View File

@@ -31,7 +31,6 @@ import { HiPSProperties } from "./HiPSProperties.js";
import { Aladin } from "./Aladin.js";
import { CooFrameEnum } from "./CooFrameEnum.js";
import { Utils } from "./Utils"
import { SpectraDisplayer } from "./SpectraDisplayer.js";
let PropertyParser = {};
// Utilitary functions for parsing the properties and giving default values
@@ -107,18 +106,6 @@ PropertyParser.skyFraction = function (properties) {
return skyFraction;
};
PropertyParser.hipsDataMinmax = function (properties) {
let data_minmax =
properties &&
properties.hips_data_minmax &&
properties.hips_data_minmax.split(" ");
const minData = data_minmax && parseFloat(data_minmax[0]);
const maxData = data_minmax && parseFloat(data_minmax[1]);
return [minData, maxData];
};
PropertyParser.cutouts = function (properties) {
let cuts =
properties &&
@@ -341,7 +328,7 @@ export let HiPS = (function () {
delete self.localFiles["properties"]
})
.catch((e) => reject("HiPS " + self.id + " error: " + self.localFiles["properties"] + " does not point towards a local HiPS.\nReason: " + e.stack))
.catch((_) => reject("HiPS " + self.id + " error: " + self.localFiles["properties"] + " does not point towards a local HiPS."))
resolve(self);
});
@@ -379,7 +366,7 @@ export let HiPS = (function () {
.then((p) => {
self._parseProperties(p);
})
.catch((e) => reject("HiPS " + self.id + " error: starting url " + self.startUrl + " given does not points to a HiPS location.\nReason: " + e.stack))
.catch((_) => reject("HiPS " + self.id + " error: starting url " + self.startUrl + " given does not points to a HiPS location"))
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
@@ -397,7 +384,7 @@ export let HiPS = (function () {
.then((p) => {
self._fetchFasterUrlFromProperties(p);
})
.catch((e) => reject("HiPS " + self.id + " error: CDS ID " + id + " is not found.\nReason: " + e.stack));
.catch((_) => reject("HiPS " + self.id + " error: CDS ID " + id + " is not found"));
},
1000
);
@@ -414,21 +401,21 @@ export let HiPS = (function () {
self._parseProperties(p);
self._fetchFasterUrlFromProperties(p);
})
.catch((_) => {
.catch(() => {
// If no ID has been found then it may actually be a path
// url pointing to a local HiPS
return HiPSProperties.fetchFromUrl(id)
.then((p) => {
self._parseProperties(p);
})
.catch((e) => reject("HiPS " + self.id + " error: " + id + " does not refer to a found CDS ID nor a local path pointing towards a HiPS.\nReason: " + e.stack))
.catch((_) => reject("HiPS " + self.id + " error: " + id + " does not refer to a found CDS ID nor a local path pointing towards a HiPS"))
})
} else {
await HiPSProperties.fetchFromUrl(self.url, self.requestMode, self.requestCredentials)
.then((p) => {
self._parseProperties(p);
})
.catch((e) => reject("HiPS " + self.id + " error: HiPS not found at url " + self.url + "\nReason: " + e.stack))
.catch((_) => reject("HiPS " + self.id + " error: HiPS not found at url " + self.url))
}
} else {
self._parseProperties({
@@ -485,21 +472,11 @@ export let HiPS = (function () {
// HiPS Cube special keywords
self.cubeDepth = properties && properties.hips_cube_depth && +properties.hips_cube_depth;
self.cubeFirstFrame = properties && properties.hips_cube_firstframe && +properties.hips_cube_firstframe;
self.emMin = properties && properties.em_min && +properties.em_min;
self.emMax = properties && properties.em_max && +properties.em_max;
if (self.emMax < self.emMin) {
let tmp = self.emMin;
self.emMin = self.emMax;
self.emMax = tmp;
}
self.hipsDataMinMax = PropertyParser.hipsDataMinmax(properties);
// HiPS3D special keywords
self.hipsOrderFreq = properties && properties.hips_order_freq && +properties.hips_order_freq;
self.hipsTileDepth = properties && properties.hips_tile_depth && +properties.hips_tile_depth;
self.obsRestFreq = properties && properties.obs_restfreq && +properties.obs_restfreq;
// Max order
const maxOrder = PropertyParser.maxOrder(properties)
@@ -579,8 +556,6 @@ export let HiPS = (function () {
return "jpeg";
} else if (acceptedFormats.indexOf("fits") >= 0) {
return "fits";
} else if (acceptedFormats.indexOf("fits.fz") >= 0) {
return "fits";
} else {
throw (
"Unsupported format(s) found in the properties: " +
@@ -675,10 +650,6 @@ export let HiPS = (function () {
this.setOptions({additive});
};
HiPS.prototype.isSpectralCube = function() {
return this.hipsTileDepth !== undefined && this.hipsTileDepth !== null;
}
/**
* Sets the colormap when rendering the HiPS.
*
@@ -801,53 +772,7 @@ export let HiPS = (function () {
this.slice = slice;
if (this.added) {
let meters = this.emMin + ((slice / this.cubeDepth) * (this.emMax - this.emMin));
let freq = 299792458.0 / meters;
this.view.wasm.setFreq(this.layer, freq);
}
}
/**
* Set the frequency to look at (for HiPS3D object only).
*
* @memberof HiPS
*
* @param {Object} [options] - frequency object
* @param {number} [options.value] = The frequency value expressed in `options.unit`
* @param {"Hz"|"m"|"m/s"} [options.unit="Hz"] - The unit of the frequency passed
* @param {number} [options.restFreq] - "The rest frequency (in Hz) to use for computing the velocity in m.s-1"
*/
HiPS.prototype.setFrequency = function(options) {
if (this.added) {
const SPEED_OF_LIGHT = 299792458.0;
const value = options && options.value;
const unit = options && options.unit;
let freq;
if (unit === "m") {
freq = SPEED_OF_LIGHT / value;
} else if (unit === "m/s") {
// A velocity is given in "m/s"
const restFreq = options && options.restFreq;
if (!restFreq) {
throw 'When giving a velocity, a rest frequency must be given as well for computing the frequency to query the HiPS'
}
freq = restFreq * (1.0 - value / SPEED_OF_LIGHT)
} else {
// unit is "Hz"
freq = value;
}
this.view.wasm.setFreq(this.layer, freq);
}
}
HiPS.prototype.getFrequency = function() {
if (this.added) {
return this.view.wasm.getFreq(this.layer);
this.view.wasm.setSliceNumber(this.layer, slice);
}
}
@@ -898,10 +823,6 @@ export let HiPS = (function () {
/// Set image format
if (options.imgFormat) {
if (this.dataproductType === "spectral-cube" && this.view.spectraDisplayer && this.view.spectraDisplayer.hips === this) {
this.view.spectraDisplayer.resetScale()
}
let imgFormat = options.imgFormat.toLowerCase();
if (imgFormat === "jpg") {
@@ -1084,8 +1005,6 @@ export let HiPS = (function () {
hipsInitialFov: self.initialFov,
hipsInitialRa: self.initialRa,
hipsInitialDec: self.initialDec,
emMin: self.emMin,
emMax: self.emMax,
// HiPS Cube
hipsCubeDepth: self.cubeDepth,
// HiPS3D

View File

@@ -26,7 +26,7 @@ import { ALEvent } from "./events/ALEvent.js";
* @property {Boolean} [perimeter=false] - Draw the perimeter of the MOC only with `options.color`.
* @property {string} [edge=!fill && !perimeter] - Draw the edges of the HEALPix cells with `options.color`.
The HEALPix cell edges compositing the MOC will be drawn if `fill` and `perimeter` are false
* @property {number} [lineWidth=1] - The line width in pixels
* @property {number} [lineWidth=3] - The line width in pixels
* @property {number} [opacity=1.0] - The opacity of the colors
*/
@@ -93,7 +93,7 @@ export let MOC = (function() {
}
this.opacity = Math.max(0, Math.min(1, this.opacity)); // 0 <= this.opacity <= 1
this.lineWidth = options["lineWidth"] || 1;
this.lineWidth = options["lineWidth"] || 3;
//this.proxyCalled = false; // this is a flag to check whether we already tried to load the MOC through the proxy

View File

@@ -66,9 +66,6 @@ export let ProgressiveCat = (function() {
this.selectionColor = options.selectionColor || '#00ff00'; // TODO: to be merged with Catalog
this.hoverColor = options.hoverColor || this.color;
// when footprints are associated to source, do we need to draw the point source as well ?
this.onlyFootprints = options.onlyFootprints ?? true;
// allows for filtering of sources
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
@@ -78,6 +75,7 @@ export let ProgressiveCat = (function() {
// we cache the list of sources in each healpix tile. Key of the cache is norder+'-'+npix
this.sourcesCache = new Utils.LRUCache(256);
this.footprintsCache = new Utils.LRUCache(256);
//added to allow hips catalogue to also use shape functions
this.updateShape(options);
@@ -212,11 +210,12 @@ export let ProgressiveCat = (function() {
newSource.setCatalog(instance);
}
instance.computeFootprints(sources);
return sources;
let footprints = instance.computeFootprints(sources);
return [sources, footprints];
};
ProgressiveCat.prototype = {
setView: function(view, idx) {
var self = this;
this.view = view;
@@ -268,8 +267,9 @@ export let ProgressiveCat = (function() {
url: self.rootUrl + '/' + 'Norder1/Allsky.tsv',
method: 'GET',
success: function(tsv) {
let sources = getSources(self, tsv, self.fields);
let [sources, footprints] = getSources(self, tsv, self.fields);
self.order1Footprints = footprints;
self.order1Sources = sources;
if (self.order2Sources) {
@@ -287,8 +287,9 @@ export let ProgressiveCat = (function() {
url: self.rootUrl + '/' + 'Norder2/Allsky.tsv',
method: 'GET',
success: function(tsv) {
let sources = getSources(self, tsv, self.fields);
let [sources, footprints] = getSources(self, tsv, self.fields);
self.order2Footprints = footprints;
self.order2Sources = sources;
if (self.order1Sources) {
@@ -320,8 +321,9 @@ export let ProgressiveCat = (function() {
self.fields = getFields(self, xml);
let sources = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
let [sources, footprints] = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
self.order2Footprints = footprints
self.order2Sources = sources
if (self.order3Sources) {
@@ -343,7 +345,8 @@ export let ProgressiveCat = (function() {
method: 'GET',
success: function(text) {
let xml = ProgressiveCat.parser.parseFromString(text, "text/xml")
let sources = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
let [sources, footprints] = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
self.order3Footprints = footprints
self.order3Sources = sources
if (self.order2Sources) {
@@ -367,7 +370,7 @@ export let ProgressiveCat = (function() {
return;
}
if (this.shapeFn) {
if (this._shapeIsFunction) {
ctx.save();
}
@@ -412,17 +415,25 @@ export let ProgressiveCat = (function() {
}
}
let key, sources;
let key, sources, footprints;
this.tilesInView.forEach((tile) => {
key = tile[0] + '-' + tile[1];
sources = this.sourcesCache.get(key);
footprints = this.footprintsCache.get(key);
if (footprints) {
footprints.forEach((f) => {
f.draw(ctx, this.view)
f.source.tooSmallFootprint = f.isTooSmall();
});
}
if (sources) {
this.drawSources(sources, ctx, width, height);
}
});
if (this.shapeFn) {
if (this._shapeIsFunction) {
ctx.restore();
}
},
@@ -464,8 +475,6 @@ export let ProgressiveCat = (function() {
});
},
getCacheCanvas: Catalog.prototype.getCacheCanvas,
drawSource: Catalog.prototype.drawSource,
getSources: function() {
@@ -493,6 +502,33 @@ export let ProgressiveCat = (function() {
return ret;
},
getFootprints: function() {
var ret = [];
if (this.order1Footprints) {
ret = ret.concat(this.order1Footprints);
}
if (this.order2Footprints) {
ret = ret.concat(this.order2Footprints);
}
if (this.order3Footprints) {
ret = ret.concat(this.order3Footprints);
}
if (this.tilesInView) {
var footprints, key, t;
for (var k=0; k < this.tilesInView.length; k++) {
t = this.tilesInView[k];
key = t[0] + '-' + t[1];
footprints = this.footprintsCache.get(key);
if (footprints) {
ret = ret.concat(footprints);
}
}
}
return ret;
},
deselectAll: function() {
if (this.order1Sources) {
@@ -521,6 +557,11 @@ export let ProgressiveCat = (function() {
for (var k=0; k<sources.length; k++) {
sources[k].deselect();
}
var footprints = this.footprintsCache[key];
for (var k=0; k<footprints.length; k++) {
footprints[k].deselect();
}
}
},
@@ -607,15 +648,17 @@ export let ProgressiveCat = (function() {
method: 'GET',
//dataType: 'jsonp',
success: function(tsv) {
let sources = getSources(self, tsv, self.fields);
let [sources, footprints] = getSources(self, tsv, self.fields);
self.sourcesCache.set(key, sources);
self.footprintsCache.set(key, footprints);
self.view.requestRedraw();
},
error: function() {
// on suppose qu'il s'agit d'une erreur 404
self.sourcesCache.set(key, []);
self.footprintsCache.set(key, []);
}
});
})(this, t[0], t[1]);

View File

@@ -63,6 +63,12 @@ export class Selector {
})
}
setMode(mode) {
if (mode) {
}
}
start(mode, callback) {
this.view.aladin.addStatusBarMessage({
id: 'selector',
@@ -106,9 +112,9 @@ export class Selector {
return;
}
const bbox = selection.bbox();
var objList = [];
var cat, sources, s;
var overlayItems, f;
var objListPerCatalog = [];
if (view.catalogs) {
for (var k = 0; k < view.catalogs.length; k++) {
@@ -118,27 +124,26 @@ export class Selector {
continue;
}
sources = cat.getSources();
for (var l = 0; l < sources.length; l++) {
s = sources[l];
if (!s.isShowing || !s.x || !s.y) {
if (!s.isShowing || !s.x || !s.y || s.tooSmallFootprint === false) {
continue;
}
// footprints
if (s.isFootprint() && s.tooSmallFootprint === false) {
if (s.footprint.intersectsBBox(bbox.x, bbox.y, bbox.w, bbox.h, view)) {
objListPerCatalog.push(s);
}
continue;
}
if (selection.contains(s)) {
objListPerCatalog.push(s);
}
}
// footprints
overlayItems = cat.getFootprints();
if (overlayItems) {
const {x, y, w, h} = selection.bbox();
for (var l = 0; l < overlayItems.length; l++) {
f = overlayItems[l];
if (f.intersectsBBox(x, y, w, h, view)) {
objListPerCatalog.push(f);
}
}
}
if (objListPerCatalog.length > 0) {
objList.push(objListPerCatalog);
@@ -148,6 +153,7 @@ export class Selector {
}
if (view.overlays) {
const {x, y, w, h} = selection.bbox();
for (var k = 0; k < view.overlays.length; k++) {
let overlay = view.overlays[k];
if (!overlay.isShowing) {
@@ -160,7 +166,7 @@ export class Selector {
continue;
}
if (o.intersectsBBox(bbox.x, bbox.y, bbox.w, bbox.h, view)) {
if (o.intersectsBBox(x, y, w, h, view)) {
objList.push([o]);
}
}

View File

@@ -49,10 +49,6 @@ export let Source = (function() {
this.isHovered = false;
};
Source.prototype.setFootprint = function(footprint) {
this.footprint = footprint;
}
Source.prototype.setCatalog = function(catalog) {
this.catalog = catalog;
};
@@ -85,13 +81,8 @@ export let Source = (function() {
if (this.isSelected) {
return;
}
this.isSelected = true;
if (this.footprint) {
this.footprint.select();
}
if (this.catalog) {
this.catalog.reportChange();
}
@@ -101,13 +92,7 @@ export let Source = (function() {
if (! this.isSelected) {
return;
}
this.isSelected = false;
if (this.footprint) {
this.footprint.deselect();
}
if (this.catalog) {
this.catalog.reportChange();
}
@@ -117,13 +102,7 @@ export let Source = (function() {
if (this.isHovered) {
return;
}
this.isHovered = true;
if (this.footprint) {
this.footprint.hover();
}
if (this.catalog) {
this.catalog.reportChange();
}
@@ -134,11 +113,6 @@ export let Source = (function() {
return;
}
this.isHovered = false;
if (this.footprint) {
this.footprint.unhover();
}
if (this.catalog) {
this.catalog.reportChange();
}
@@ -152,18 +126,6 @@ export let Source = (function() {
this.shape = shape;
}
Source.prototype.setColor = function(color) {
this.color = color;
if (this.footprint) {
this.footprint.setColor(color);
}
}
Source.prototype.setSize = function(size) {
this.size = Math.max(size, 1.0);
}
/**
* Simulates a click on the source
*
@@ -215,17 +177,13 @@ export let Source = (function() {
};
Source.prototype.isFootprint = function() {
return this.footprint !== undefined && this.footprint !== null;
return false;
}
Source.prototype.actionOtherObjectClicked = function() {
if (this.catalog && this.catalog.onClick) {
this.deselect();
}
if (this.footprint) {
this.footprint.deselect()
}
};
return Source;

View File

@@ -1,779 +0,0 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { ActionButton } from "./gui/Widgets/ActionButton";
import { Input } from "./gui/Widgets/Input";
import HomeIconUrl from '../../assets/icons/maximize.svg';
import SpectraIconUrl from '../../assets/icons/freq.svg';
import { ALEvent } from "./events/ALEvent";
import { Utils } from "./Utils";
import { Aladin } from "./Aladin";
/******************************************************************************
* Aladin Lite project
*
* File SpectraDisplayer.js
*
*
* Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr]
*
*****************************************************************************/
export class SpectraDisplayer {
static UNIT = {
FREQUENCY: {
label: "f",
units: [
{ unit: "THz", factor: 1e12 },
{ unit: "GHz", factor: 1e9 },
{ unit: "MHz", factor: 1e6 },
{ unit: "kHz", factor: 1e3 },
{ unit: "Hz", factor: 1 }
]
},
WAVELENGTH: {
label: "λ",
units: [
{ unit: "km", factor: 1e3 },
{ unit: "m", factor: 1 },
{ unit: "mm", factor: 1e-3 },
{ unit: "μm", factor: 1e-6 },
{ unit: "nm", factor: 1e-9 },
{ unit: "Å", factor: 1e-10 },
{ unit: "pm", factor: 1e-12 },
]
},
VELOCITY: {
label: "v",
units: [
{ unit: "km/s", factor: 1e3 },
{ unit: "m/s", factor: 1 },
{ unit: "mm/s", factor: 1e-3 },
{ unit: "μm/s", factor: 1e-6 },
{ unit: "nm/s", factor: 1e-9 },
{ unit: "pm/s", factor: 1e-12 },
]
},
convertFrequency: function(freq, options) {
const SPEED_OF_LIGHT = 299792458.0;
let unit = options && options.unit;
let value;
if (unit === SpectraDisplayer.UNIT.WAVELENGTH) {
value = SPEED_OF_LIGHT / freq;
} else if (unit === SpectraDisplayer.UNIT.VELOCITY) {
// A velocity is given in "m/s"
const restFreq = options && options.restFreq;
if (!restFreq) {
throw 'When giving a velocity, a rest frequency must be given as well for computing the frequency to query the HiPS'
}
value = SPEED_OF_LIGHT * (1.0 - freq / restFreq)
} else {
// unit is "Hz"
value = freq;
}
return value;
},
convertPrecisionFrequency: function(prec, freq, options) {
const SPEED_OF_LIGHT = 299792458.0;
let unit = options && options.unit;
let value;
if (unit === SpectraDisplayer.UNIT.WAVELENGTH) {
value = SPEED_OF_LIGHT * prec / (freq * freq);
} else if (unit === SpectraDisplayer.UNIT.VELOCITY) {
// A velocity is given in "m/s"
const restFreq = options && options.restFreq;
if (!restFreq) {
throw 'When giving a velocity, a rest frequency must be given as well for computing the frequency to query the HiPS'
}
value = prec * SPEED_OF_LIGHT / restFreq
} else {
// unit is "Hz"
value = prec;
}
return value;
}
};
constructor(view, options) {
let createPlotCanvas = (name) => {
const canvas = document.createElement("canvas");
canvas.classList.add(name);
canvas.width = this.width;
canvas.height = this.height;
canvas.style.position = "absolute"
canvas.style.top = 0;
canvas.style.left = 0;
this.view.aladinDiv.appendChild(canvas);
return canvas;
};
this.view = view;
this.data = undefined;
this.scaleX = undefined;
this.scaleY = undefined;
this.height = options && options.height || 300;
this.width = options && options.width || 600;
this.minY = undefined;
this.maxY = undefined;
this.mouseFreq = undefined;
// One canvas for the spectra
this.canvas = createPlotCanvas("spectra-line");
this.ctx = this.canvas.getContext("2d");
// One canvas for the mouse hover spectral line
const canvasCursor = createPlotCanvas('spectra-cursor')
canvasCursor.style.pointerEvents = "none"
this.ctxCursor = canvasCursor.getContext("2d");
// One canvas for text
const canvasLabels = createPlotCanvas('spectra-labels')
canvasLabels.style.pointerEvents = "none"
this.ctxLabels = canvasLabels.getContext("2d");
let self = this;
// a selector for choosing the unit
let unitSelector = new Input({
label: "Unit:",
name: "unit selector",
value: "f",
type: 'select',
classList: ['aladin-spectra-unit-selector'],
options: [
SpectraDisplayer.UNIT.FREQUENCY.label,
SpectraDisplayer.UNIT.WAVELENGTH.label,
SpectraDisplayer.UNIT.VELOCITY.label,
],
tooltip: {
content: "Unit between frequency, wavelength and velocity",
position: {direction: "right"}
},
change: (e) => {
let label = e.target.value;
if (label === SpectraDisplayer.UNIT.FREQUENCY.label) {
self.unit = SpectraDisplayer.UNIT.FREQUENCY
} else if (label === SpectraDisplayer.UNIT.WAVELENGTH.label) {
self.unit = SpectraDisplayer.UNIT.WAVELENGTH
} else {
self.unit = SpectraDisplayer.UNIT.VELOCITY
}
self._redrawLabels();
},
})
let autoCenterBtn = new ActionButton({
size: 'small',
icon: {
monochrome: true,
url: HomeIconUrl
},
tooltip: {
content: "Scale for data",
position: {direction: "right"}
},
classList: ['aladin-spectra-home'],
action(e) {
self.resetScale()
self._redraw(self.ctx);
}
})
let extractionBtn = new ActionButton({
size: 'small',
icon: {
monochrome: true,
url: SpectraIconUrl
},
tooltip: {
content: "Extract the spectra under the cursor",
position: {direction: "right"}
},
classList: ['aladin-spectra-extraction'],
action(e) {
// TODO
}
})
this.unit = SpectraDisplayer.UNIT.FREQUENCY;
let divNode = document.createElement("div");
divNode.style.position = "absolute";
divNode.style.left = "50%";
divNode.style.transform = "translateX(-50%)";
divNode.style.bottom = "2px";
divNode.style.width = this.width + "px";
divNode.style.height = this.height + "px";
divNode.classList.add("aladin-lite-spectra-displayer")
divNode.appendChild(this.canvas)
divNode.appendChild(canvasCursor)
divNode.appendChild(canvasLabels)
divNode.appendChild(unitSelector.element())
divNode.appendChild(autoCenterBtn.element())
//divNode.appendChild(extractionBtn.element())
this.divNode = divNode;
this.view.aladin.aladinDiv.appendChild(divNode);
this.defineEventListeners()
this.hips3DList = new Map();
}
defineEventListeners() {
let lastMouse = { x: 0, y: 0 };
this.isDragging = false;
let canvas = this.canvas;
let ctxCursor = this.ctxCursor;
let self = this;
let lastClickTime = 0;
const DOUBLE_CLICK_DELAY = 300; // most operating systems uses duration between 250ms and 500ms by default.
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
let v = this.data.values[Math.round(mx / this.scaleX)]
let len = this.data.values.length;
v = this.height - (v - this.minY) * this.scaleY
if (my >= v) {
this.isDragging = true;
lastMouse = { x: mx, y: my };
canvas.style.cursor = 'grabbing';
} else {
// check if the click is next to the center bar
// Draw the vertical line that can be grabed to move the slice
this.ctx.beginPath();
this.ctx.moveTo(this.scaleX * len / 2, this.height);
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
this.ctx.strokeStyle = Aladin.DEFAULT_OPTIONS.reticleColor;
this.ctx.lineWidth = 10;
if (this.ctx.isPointInStroke(mx, my)) {
this.isDragging = true;
lastMouse = { x: mx, y: my };
canvas.style.cursor = 'grabbing';
} else {
// propagate event to its sibling
let paramsEvent = {
bubbles: e.bubbles,
cancelable: e.cancelable,
clientX: e.clientX,
clientY: e.clientY,
screenX: e.screenX,
screenY: e.screenY,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
button: e.button,
relatedTarget: e.relatedTarget,
};
const event = new MouseEvent('mousedown', paramsEvent);
// Track timing to simulate dblclick
const now = Date.now();
if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
const dblClickEvent = new MouseEvent('dblclick', {
bubbles: true,
cancelable: true,
clientX: e.clientX,
clientY: e.clientY
});
this.view.catalogCanvas.dispatchEvent(dblClickEvent);
lastClickTime = 0; // reset
} else {
lastClickTime = now;
}
this.view.catalogCanvas.dispatchEvent(event);
}
}
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
// can be in the spectral area
let v = this.data.values[Math.round(mx / this.scaleX)]
let len = this.data.values.length;
v = this.height - (v - this.minY) * this.scaleY
canvas.style.cursor = 'default';
this.ctxCursor.clearRect(0, 0, this.width, this.height);
this.mouseFreq = null;
if (my >= v) {
canvas.style.cursor = 'grab';
ctxCursor.beginPath();
ctxCursor.moveTo(mx, this.height);
ctxCursor.lineTo(mx, v);
ctxCursor.strokeStyle = "yellow";
ctxCursor.lineWidth = 2;
ctxCursor.stroke()
// compute the frequency at that position
let curFreq = self.hips.getFrequency();
let curHash = Number(self.view.wasm.freq2hash(self.hips.layer, curFreq));
let mouseHash = curHash + Math.round((mx - (this.width / 2)) / this.scaleX)
this.mouseFreq = self.view.wasm.hash2freq(self.hips.layer, BigInt(mouseHash));
}
this._redrawLabels()
if (!this.isDragging) {
// Draw the vertical line that can be grabed to move the slice
this.ctx.beginPath();
this.ctx.moveTo(this.scaleX * len / 2, this.height);
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
this.ctx.strokeStyle = "red";
this.ctx.lineWidth = 10;
if (this.ctx.isPointInStroke(mx, my)) {
this.canvas.style.cursor = 'grab';
}
return;
}
this.mouseFreq = null;
this.canvas.style.cursor = 'grabbing';
// is dragged
let dx = (mx - lastMouse.x) / this.scaleX;
if (dx != 0) {
// Set the frequency
// look where we are in the freq range
let j = Utils.binarySearch(self.data.freqs, self.data.freq);
let df, f;
if (j > 0 && j < self.data.freqs.length - 1) {
df = (self.data.freqs[j + 1] - self.data.freqs[j - 1]) * 0.5;
f = self.data.freq - dx * df;
} else if (j == 0) {
df = self.data.freqs[1] - self.data.freqs[0]
f = self.data.freqs[0] - dx * df;
} else {
df = self.data.freqs[self.data.freqs.length - 1] - self.data.freqs[self.data.freqs.length - 2];
f = self.data.freqs[self.data.freqs.length - 1] - dx * df;
}
self.hips.setFrequency({
value: f,
unit: 'Hz'
})
lastMouse = { x: mx, y: my };
}
});
canvas.addEventListener('mouseup', (e) => {
this.isDragging = false;
canvas.style.cursor = 'default';
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: e.clientX,
clientY: e.clientY
});
this.view.catalogCanvas.dispatchEvent(clickEvent);
});
canvas.addEventListener('mouseout', (e) => {
this.isDragging = false;
});
canvas.addEventListener('wheel', (e) => {
this.ctxCursor.clearRect(0, 0, this.width, this.height);
const wheelEvent = new WheelEvent('wheel', {
bubbles: true,
cancelable: true,
deltaX: e.deltaX,
deltaY: e.deltaY,
deltaMode: e.deltaMode,
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey
});
this.view.catalogCanvas.dispatchEvent(wheelEvent);
});
/*
const updateSelectorList = () => {
let options = [];
for (const hipsName of this.hips3DList.keys()) {
options.push(hipsName)
}
this.selector.update({options})
};
ALEvent.HIPS_LAYER_ADDED.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let hips = e.detail.layer;
if (hips.dataproductType === "spectral-cube") {
self.hips3DList.set(hips.name, hips);
updateSelectorList()
}
}
);
ALEvent.HIPS_LAYER_SWAP.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let firstHiPS = e.detail.firstLayer;
let secondHiPS = e.detail.secondLayer;
self.hips3DList.delete(firstHiPS.name);
if (secondHiPS.dataproductType === "spectral-cube") {
self.hips3DList.set(secondHiPS.name, secondHiPS);
}
updateSelectorList()
}
);
ALEvent.HIPS_LAYER_REMOVED.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let hips = e.detail.layer;
self.hips3DList.delete(hips.name);
if (hips === this.hips) {
// the hips pointed by the tool has been removed
self.attachHiPS3D(null);
}
if (self.hips3DList.size === 0) {
self.hide()
}
updateSelectorList()
}
);*/
}
hide() {
if (this.isHidden) {
return;
}
this.divNode.style.display = "none";
this.isHidden = true;
}
show() {
if (!this.isHidden) {
return;
}
this.divNode.style.display = "block";
this.isHidden = false;
}
attachHiPS3D(hips) {
// remove the callback from the last hips if there is
if (this.spectraUpdateCallback) {
window.removeEventListener("spectra", this.spectraUpdateCallback)
}
// store new references to the new hips
this.hips = hips;
if (hips) {
this.spectraUpdateCallback = (event) => {
let data = event.detail;
if (data.layer === this.hips.layer) {
this.data = data;
this._redraw(this.ctx);
}
};
window.addEventListener("spectra", this.spectraUpdateCallback);
this.resetScale();
this.show()
}
}
// When changing the HiPS format, a scale reset is necessary
resetScale() {
this.minY = undefined;
this.maxY = undefined;
}
enableInteraction() {
this.divNode.style.pointerEvents = "auto"
}
disableInteraction() {
this.divNode.style.pointerEvents = "none"
}
_redraw() {
const values = this.data.values;
let len = values.length;
// Clear previous drawing
this.ctx.clearRect(0, 0, this.width, this.height);
// Find min and max for scaling
let valuesWithNoNans = values.filter(v=>Number.isFinite(v));
if (Number.isFinite(this.minY)) {
this.minY = Math.min(...valuesWithNoNans, this.minY)
} else {
this.minY = Math.min(...valuesWithNoNans)
}
if (Number.isFinite(this.maxY)) {
this.maxY = Math.max(...valuesWithNoNans, this.maxY)
} else {
this.maxY = Math.max(...valuesWithNoNans)
}
this.scaleX = this.width / (len - 1);
this.scaleY = (this.maxY - this.minY === 0) ? 1 : this.height / (this.maxY - this.minY);
this._redrawSpectra(values)
// Draw the vertical line that can be grabed to move the slice
this.ctx.beginPath();
this.ctx.moveTo(this.scaleX * len / 2, this.height);
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
this.ctx.strokeStyle = Aladin.DEFAULT_OPTIONS.reticleColor;
this.ctx.lineWidth = 2;
this.ctx.stroke();
this._redrawLabels()
}
_redrawLabels() {
let self = this;
let spectraValue2String = (freq, precision) => {
let units = self.unit.units;
let x = SpectraDisplayer.UNIT.convertFrequency(
freq,
{
unit: self.unit,
restFreq: self.hips.obsRestFreq
}
)
let dx = SpectraDisplayer.UNIT.convertPrecisionFrequency(
precision,
freq,
{
unit: self.unit,
restFreq: self.hips.obsRestFreq
}
)
for (const { unit, factor } of units) {
const value = x / factor;
const precisionInUnit = dx / factor;
if (Math.abs(value) >= 1 || unit === units[units.length - 1].unit) {
// Calculate number of decimal places needed to show the given precision
const decimals = Math.max(0, Math.ceil(-Math.log10(precisionInUnit)));
return value.toFixed(decimals) + " " + unit;
}
}
}
// Clear previous drawing
this.ctxLabels.clearRect(0, 0, this.width, this.height);
let drawLabel = (ctx, str, x, y, strokeStyle, font, fillStyle) => {
ctx.strokeStyle = strokeStyle; // contour color
ctx.strokeText(str, x, y);
ctx.fillStyle = fillStyle;
ctx.font = font
ctx.fillText(str, x, y);
};
// Draw the min and max frequencies
this.ctxLabels.font = "20px monospace"; // You can also use "Courier New", "Consolas", etc.
this.ctxLabels.fillStyle = "lightgreen";
this.ctxLabels.textBaseline = "middle"; // Vertically centered
// min window freq
this.ctxLabels.textAlign = "left"; // Horizontally centered
drawLabel(
this.ctxLabels,
spectraValue2String(this.data.freqMin, this.data.freqs[1] - this.data.freqs[0]),
0,
this.height - 20,
'black',
'20px monospace',
'lightgreen'
)
// max window freq
this.ctxLabels.textAlign = "right"; // Horizontally centered
drawLabel(
this.ctxLabels,
spectraValue2String(this.data.freqMax, this.data.freqs[this.data.freqs.length - 1] - this.data.freqs[this.data.freqs.length - 2]),
this.width,
this.height - 20,
'black',
'20px monospace',
'lightgreen'
)
// current window freq
this.ctxLabels.textAlign = "center"; // Horizontally centered
let str, fillStyle;
if (!this.isDragging && this.mouseFreq) {
fillStyle = "yellow";
str = spectraValue2String(this.mouseFreq, this.data.freqStep);
} else {
fillStyle = Aladin.DEFAULT_OPTIONS.reticleColor;
str = spectraValue2String(this.data.freq, this.data.freqStep);
}
drawLabel(
this.ctxLabels,
str,
this.width / 2,
this.height - 20,
'black',
'20px monospace',
fillStyle
)
}
_redrawSpectra(array) {
this.ctx.beginPath();
this.ctx.lineWidth = 4;
let strokeStyle = "red";
this.ctx.strokeStyle = strokeStyle
let prevY;
let i = 0;
let i1 = array.length;
while (i < i1) {
let y;
const x = i * this.scaleX;
const inValidDomain = this.data.freqIdxStart !== undefined && this.data.freqIdxEnd !== undefined && i >= this.data.freqIdxStart && i <= this.data.freqIdxEnd;
if (inValidDomain) {
const tileNotReceived = !Number.isFinite(array[i]);
if (tileNotReceived) {
// color orange
if (strokeStyle !== "orange") {
this.ctx.lineTo(x, this.height)
strokeStyle = "orange"
this.ctx.stroke()
this.ctx.beginPath();
this.ctx.strokeStyle = strokeStyle
this.ctx.lineWidth = 4
}
y = this.height;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
} else {
// valid frequency, color green
if (strokeStyle !== "lightgreen") {
strokeStyle = "lightgreen"
this.ctx.stroke()
this.ctx.beginPath();
this.ctx.strokeStyle = strokeStyle
this.ctx.lineWidth = 2
this.ctx.moveTo(x - this.scaleX, prevY)
}
y = this.height - (array[i] - this.minY) * this.scaleY;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
} else {
// frequency out of the survey coverage => color red
if (strokeStyle !== "red") {
this.ctx.lineTo(x, this.height)
this.ctx.stroke()
this.ctx.beginPath();
strokeStyle = "red"
this.ctx.strokeStyle = strokeStyle
this.ctx.lineWidth = 4
}
y = this.height;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
i++;
prevY = y;
}
this.ctx.stroke();
}
}

View File

@@ -54,11 +54,11 @@ export let URLBuilder = (function() {
},
buildNEDPositionCSURL: function(ra, dec, radiusDegrees) {
return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Position+Search&of=xml_main&RA=' + ra + '&DEC=' + dec + '&SR=' + radiusDegrees;
return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Position+Search&of=xml_main&RA=' + ra + '&DEC=' + dec + '&SR=' + radiusDegrees;
},
buildNEDObjectCSURL: function(object, radiusDegrees) {
return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Name+Search&radius=' + (60 * radiusDegrees) + '&of=xml_main&objname=' + object;
return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Name+Search&radius=' + (60 * radiusDegrees) + '&of=xml_main&objname=' + object;
},
buildSKAORucioCSURL: function(target, radiusDegrees) {

View File

@@ -149,18 +149,12 @@ Utils.inverseNewtonRaphson = function(y: number, f: Function, fPrime: Function,
Utils.binarySearch = function(array, value) {
var low = 0,
high = array.length;
var mid;
while (low < high) {
mid = Math.floor((low + high) / 2);
if (array[mid] === value) {
return mid;
} else if (array[mid] < value) {
low = mid + 1;
} else {
high = mid;
}
}
while (low < high) {
var mid = (low + high) >>> 1;
if (array[mid] > value) low = mid + 1;
else high = mid;
}
return low;
}

View File

@@ -44,10 +44,12 @@ import { Zoom } from './Zoom.js'
import { Footprint } from "./Footprint.js";
import { Selector } from "./Selector.js";
import { ObsCore } from "./vo/ObsCore.js";
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
import { Layout } from "./gui/Layout.js";
import { SAMPActionButton } from "./gui/Button/SAMP.js";
import { HiPS } from "./HiPS.js";
import { Image } from "./Image.js";
import { Color } from "./Color.js";
import { SpectraDisplayer } from "./SpectraDisplayer.js";
export let View = (function () {
@@ -211,7 +213,7 @@ export let View = (function () {
this.fov = this.options.fov || 180.0
// Target position settings
this.viewCenter = { ra: lon, dec: lat }; // position of center of view always in ICRS
this.viewCenter = { lon, lat }; // position of center of view
// Coo frame setting
const cooFrame = CooFrameEnum.fromString(this.options.cooFrame, CooFrameEnum.ICRS);
@@ -590,20 +592,11 @@ export let View = (function () {
}
View.prototype.selectLayer = function (layer) {
let imageLayer = this.imageLayers.get(layer)
if (!imageLayer) {
if (!this.imageLayers.has(layer)) {
console.warn(layer + ' does not exists. So cannot be selected');
return;
}
if (imageLayer.dataproductType === "spectral-cube") {
if (!this.spectraDisplayer) {
this.spectraDisplayer = new SpectraDisplayer(this, {width: 800, height: 300});
}
this.spectraDisplayer.attachHiPS3D(imageLayer)
}
this.selectedLayer = layer;
};
@@ -629,6 +622,7 @@ export let View = (function () {
catch (err) {
return;
}
};
if (!Utils.hasTouchScreen()) {
@@ -726,10 +720,6 @@ export let View = (function () {
const xymouse = Utils.relMouseCoords(e);
if (view.spectraDisplayer) {
view.spectraDisplayer.disableInteraction();
}
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
state: {
mode: view.mode,
@@ -798,7 +788,6 @@ export let View = (function () {
view.dragCoo = xymouse;
view.dragging = true;
view.aladin.contextMenu && view.aladin.contextMenu._hide()
if (view.mode === View.PAN) {
@@ -816,7 +805,6 @@ export let View = (function () {
return true;
});
/*
Utils.on(view.catalogCanvas, "mouseup", function (e) {
e.preventDefault();
e.stopPropagation();
@@ -847,36 +835,9 @@ export let View = (function () {
view.selector.dispatch('mouseup', {coo: xymouse})
}
});
*/
Utils.on(view.catalogCanvas, "click", function (e) {
// call listener of 'click' event
if (view.mode == View.TOOL_SIMBAD_POINTER) {
// call Simbad pointer or Planetary features
GenericPointer(view, e);
return; // when in TOOL_SIMBAD_POINTER mode, we do not call the listeners
}
if (view.mode == View.TOOL_COLOR_PICKER) {
Utils.copy2Clipboard(view.colorPickerTool.probedValue)
.then(() => {
if (view.aladin.statusBar) {
view.aladin.statusBar.appendMessage({
message: `${view.colorPickerTool.probedValue} copied into your clipboard`,
duration: 1500,
type: 'info'
})
}
})
return; // listeners are not called
}
});
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
Utils.on(view.catalogCanvas, "click mouseout touchend touchcancel", function (e) {
const xymouse = Utils.relMouseCoords(e);
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
@@ -921,9 +882,6 @@ export let View = (function () {
}
view.dragging = false;
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
if (wasDragging) {
view.realDragging = false;
@@ -936,12 +894,6 @@ export let View = (function () {
view.mustClearCatalog = true;
view.dragCoo = null;
if (e.type === "mouseup") {
if (view.mode === View.SELECT) {
view.selector.dispatch('mouseup', {coo: xymouse})
}
}
if (e.type === "mouseout" || e.type === "touchend" || e.type === "touchcancel") {
if (e.type === "mouseout" || e.type === "touchcancel") {
if (view.mode === View.SELECT) {
@@ -960,14 +912,25 @@ export let View = (function () {
}
}
if (view.rightClick) {
if (showContextMenu) {
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
}
if (view.mode == View.TOOL_SIMBAD_POINTER) {
// call Simbad pointer or Planetary features
GenericPointer(view, e);
view.rightClick = false;
return; // when in TOOL_SIMBAD_POINTER mode, we do not call the listeners
}
return;
if (view.mode == View.TOOL_COLOR_PICKER) {
Utils.copy2Clipboard(view.colorPickerTool.probedValue)
.then(() => {
if (view.aladin.statusBar) {
view.aladin.statusBar.appendMessage({
message: `${view.colorPickerTool.probedValue} copied into your clipboard`,
duration: 1500,
type: 'info'
})
}
})
return; // listeners are not called
}
// popup to show ?
@@ -987,6 +950,7 @@ export let View = (function () {
}
}
// call listener of 'click' event
var onClickFunction = view.aladin.callbacksByEventName['click'];
if (typeof onClickFunction === 'function') {
var pos = view.aladin.pix2world(xymouse.x, xymouse.y, "icrs");
@@ -995,14 +959,16 @@ export let View = (function () {
}
}
if (view.mode === View.SELECT && e.type === "click") {
view.selector.dispatch('click', {coo: xymouse})
}
// TODO : remplacer par mecanisme de listeners
// on avertit les catalogues progressifs
view.refreshProgressiveCats();
//view.requestRedraw();
view.wasm.releaseLeftButtonMouse();
if (view.mode === View.SELECT && e.type === "click") {
view.selector.dispatch('click', {coo: xymouse})
}
});
var lastHoveredObject; // save last object hovered by mouse
@@ -1045,7 +1011,6 @@ export let View = (function () {
view.colorPickerTool.domElement.style.left = `${xymouse.x}px`;
view.colorPickerTool.domElement.style.top = `${xymouse.y}px`;
}
Utils.on(view.catalogCanvas, "mousemove touchmove", function (e) {
e.preventDefault();
@@ -1176,7 +1141,6 @@ export let View = (function () {
view.setCursor('pointer');
for (let o of closests) {
if (typeof objHoveredFunction === 'function' && (!lastHoveredObject || !lastHoveredObject.includes(o))) {
var ret = objHoveredFunction(o, xymouse);
}
@@ -1383,6 +1347,8 @@ export let View = (function () {
};
};
View.FPS_INTERVAL = 1000 / 140;
/**
* redraw the whole view
*/
@@ -1409,6 +1375,8 @@ export let View = (function () {
this.drawAllOverlays();
}
this.needRedraw = false;
//this.then = now % View.FPS_INTERVAL;
};
View.prototype.drawAllOverlays = function () {
@@ -1636,14 +1604,9 @@ export let View = (function () {
return source;
});
let tableColor = catalog.color;
if (catalog.colorFn) {
tableColor = "white"
}
let table = {
'name': catalog.name,
'color': tableColor,
'color': catalog.color,
'rows': sources,
'fields': catalog.fields,
'showCallback': ObsCore.SHOW_CALLBACKS(this.aladin)
@@ -1778,7 +1741,6 @@ export let View = (function () {
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layerName);
// Notify that this image layer has been replaced by the wasm part
if (alreadyPresentImageLayer && alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
@@ -1793,9 +1755,9 @@ export let View = (function () {
this.imageLayers.set(layerName, imageLayer);
// select the layer if he is on top
//if (idxOverlayLayer == -1) {
if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
//}
}
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -2060,17 +2022,9 @@ export let View = (function () {
};
View.prototype.updateCenter = function() {
// Center position in the frame of the view
const [lon, lat] = this.wasm.getCenter();
// ICRS conversion
let [ra, dec] = this.wasm.viewToICRSCooSys(lon, lat);
if (ra < 0) {
ra = ra + 360.0
}
this.viewCenter = {ra, dec};
const [ra, dec] = this.wasm.getCenter();
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
}
View.prototype.showHealpixGrid = function (show) {
@@ -2115,10 +2069,11 @@ export let View = (function () {
return;
}
this.viewCenter = {ra, dec};
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
// Put a javascript code here to do some animation
this.wasm.setCenter(this.viewCenter.ra, this.viewCenter.dec);
this.wasm.setCenter(this.viewCenter.lon, this.viewCenter.lat);
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
@@ -2239,7 +2194,7 @@ export let View = (function () {
continue;
}
if (s.isFootprint() && cat.onlyFootprints && !s.tooSmallFootprint) {
if (s.hasFootprint === true && s.tooSmallFootprint === false) {
continue;
}
@@ -2266,16 +2221,20 @@ export let View = (function () {
let closests = [];
footprints.forEach((footprint) => {
const originLineWidth = footprint.getLineWidth();
let spreadedLineWidth = (originLineWidth || 1) + 3;
footprint.setLineWidth(spreadedLineWidth);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
closests.push(footprint);
if (!footprint.source || !footprint.source.tooSmallFootprint) {
const originLineWidth = footprint.getLineWidth();
let spreadedLineWidth = (originLineWidth || 1) + 3;
footprint.setLineWidth(spreadedLineWidth);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
closests.push(footprint);
}
footprint.setLineWidth(originLineWidth);
}
footprint.setLineWidth(originLineWidth);
})
return closests;
};
@@ -2299,50 +2258,48 @@ export let View = (function () {
if (this.catalogs) {
for (var k = 0; k < this.catalogs.length; k++) {
let catalog = this.catalogs[k];
let footprints = catalog.getFootprints();
for (var s of catalog.getSources()) {
if (s.isFootprint() && !s.tooSmallFootprint) {
let footprint = s.footprint;
const originLineWidth = footprint.getLineWidth();
let spreadedLineWidth = (originLineWidth || 1) + 3;
footprint.setLineWidth(spreadedLineWidth);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
closests.push(s);
}
footprint.setLineWidth(originLineWidth);
}
}
closests = closests.concat(this.closestFootprints(footprints, ctx, x, y));
}
}
if (!this.objLookup) {
//ctx.lineWidth = pastLineWidth;
return null;
}
//ctx.lineWidth = pastLineWidth;
var dist = Number.POSITIVE_INFINITY;
var closest = null;
for (var dx = -maxRadius; dx <= maxRadius; dx++) {
if (!this.objLookup[x + dx]) {
continue;
}
for (var dy = -maxRadius; dy <= maxRadius; dy++) {
if (this.objLookup[x + dx][y + dy]) {
var d = dx * dx + dy * dy;
if (d < dist) {
dist = d;
closest = this.objLookup[x + dx][y + dy]
} else if (d == dist) {
closest.concat(this.objLookup[x + dx][y + dy])
//for (var r = 0; r <= maxRadius; r++) {
//closest = dist = null;
for (var dx = -maxRadius; dx <= maxRadius; dx++) {
if (!this.objLookup[x + dx]) {
continue;
}
for (var dy = -maxRadius; dy <= maxRadius; dy++) {
if (this.objLookup[x + dx][y + dy]) {
var d = dx * dx + dy * dy;
if (d < dist) {
dist = d;
closest = this.objLookup[x + dx][y + dy]
} else if (d == dist) {
closest.concat(this.objLookup[x + dx][y + dy])
}
}
}
}
}
if (closest && closest.length > 0) {
closests = closests.concat(closest)
}
if (closest && closest.length > 0) {
closests = closests.concat(closest)
}
/*if (closest) {
closests = closests.concat(closest);
}*/
//}
if (closests.length === 0)
return null;

View File

@@ -47,7 +47,6 @@ export class ALEvent {
static UPDATE_CMAP_LIST = new ALEvent("AL:cmap.updated");
// Gives the center position in ICRS
static POSITION_CHANGED = new ALEvent("AL:position.changed");
static ZOOM_CHANGED = new ALEvent("AL:zoom.changed");
@@ -71,7 +70,7 @@ export class ALEvent {
static SAMP_CONNECTED = new ALEvent("AL:samp.connected");
static SAMP_DISCONNECTED = new ALEvent("AL:samp.disconnected");
static CANVAS_EVENT = new ALEvent("AL:Event");
static CANVAS_EVENT = new ALEvent("AL:Event");
static RETICLE_CHANGED = new ALEvent("AL:Reticle.changed")

View File

@@ -35,15 +35,13 @@ import { Form } from "../Widgets/Form.js";
import colorIconUrl from '../../../../assets/icons/color.svg';
import pixelHistIconUrl from '../../../../assets/icons/pixel_histogram.svg';
import { RadioButton } from "../Widgets/Radio.js";
import waveOnIconUrl from '../../../../assets/icons/wave-on.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
export class HiPSSettingsBox extends Box {
// Constructor
constructor(aladin, options) {
let self;
let selector = new RadioButton({
luminosity: {
icon: {
@@ -54,7 +52,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
tooltip: {content: 'Contrast', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({
layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.luminositySettingsContent]
layout: [self.selector, self.luminositySettingsContent]
});
self.update({content})
}
@@ -67,7 +65,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Opacity', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent]});
const content = Layout.vertical({layout: [self.selector, self.opacitySettingsContent]});
self.update({content})
}
},
@@ -78,7 +76,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Colormap', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.colorSettingsContent]});
const content = Layout.vertical({layout: [self.selector, self.colorSettingsContent]});
self.update({content})
}
},
@@ -90,7 +88,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Cutouts', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.pixelSettingsContent]});
const content = Layout.vertical({layout: [self.selector, self.pixelSettingsContent]});
self.update({content})
}
},
@@ -330,31 +328,6 @@ import { TogglerActionButton } from "../Button/Toggler.js";
update(options) {
if (options.layer) {
let self = this;
if (options.layer.isSpectralCube()) {
let spectraDisplayer = self.aladin.view.spectraDisplayer;
self.spectraBtn = new TogglerActionButton({
content: 'Spectra',
icon: {
size: 'small',
monochrome: true,
url: waveOnIconUrl
},
tooltip: {content: 'Show/hide spectra', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
spectraDisplayer.attachHiPS3D(options.layer)
spectraDisplayer.show()
},
actionOff: () => {
spectraDisplayer.hide()
}
});
self.update({content: Layout.vertical([Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent])})
}
this._update(options.layer)
}
@@ -362,12 +335,9 @@ import { TogglerActionButton } from "../Button/Toggler.js";
}
_addListeners() {
let self = this;
ALEvent.HIPS_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, (e) => {
const hips = e.detail.layer;
let selectedLayer = this.options.layer;
if (selectedLayer && hips.layer === selectedLayer.layer) {
this._update(hips)
}

View File

@@ -83,7 +83,6 @@ export class OverlayStackBox extends Box {
sourceSize: 8,
color: "#318d80",
hoverColor: 'red',
onlyFootprints: false,
onClick: "showTable",
shape: (s) => {
let galaxy = ["Seyfert","Seyfert_1", "Seyfert_2","LSB_G","PartofG","RadioG","Gin","GinPair","HII_G","LensedG","BClG","BlueCompG","EmG","GinCl","GinGroup","StarburstG","LINER","AGN", "Galaxy", "GtowardsGroup", "GtowardsCl", "BrightestCG"].some((n) => s.data.main_type.indexOf(n) >= 0);
@@ -305,6 +304,7 @@ export class OverlayStackBox extends Box {
let moc = A.MOCFromURL(url, {
name: file.name,
lineWidth: 3.0,
});
self.aladin.addMOC(moc);
},
@@ -347,6 +347,7 @@ export class OverlayStackBox extends Box {
{ ra, dec, radius },
{
name: "cone",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);
@@ -417,6 +418,7 @@ export class OverlayStackBox extends Box {
},
{
name: "rect",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);
@@ -461,6 +463,7 @@ export class OverlayStackBox extends Box {
{ ra, dec },
{
name: "poly",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);
@@ -1159,14 +1162,9 @@ export class OverlayStackBox extends Box {
}
// retrieve SVG icon, and apply the layer color
let color = overlay.color;
if (overlay.colorFn) {
color = "white"
}
return new Icon({
size: "small",
url: Icon.dataURLFromSVG({ svg, color }),
url: Icon.dataURLFromSVG({ svg, color: overlay.color }),
tooltip,
});
}

View File

@@ -41,16 +41,6 @@ import { ActionButton } from "./Widgets/ActionButton.js";
import { Input } from "./Widgets/Input.js";
import { Utils } from "../Utils.ts";
function radec2Lonlat(radec, frame) {
// convert to the view frame
let lonlat = radec;
if (frame === "GAL") {
lonlat = CooConversion.ICRSToGalactic(radec)
}
return lonlat
}
export class Location extends DOMElement {
// constructor
constructor(aladin) {
@@ -151,15 +141,20 @@ export class Location extends DOMElement {
ALEvent.CANVAS_EVENT.listenedBy(aladin.aladinDiv, function (e) {
let param = e.detail;
let frame = aladin.getFrame();
if (param.type === 'mouseout') {
let [ra, dec] = aladin.getRaDec();
let radec = aladin.getRaDec();
// convert to the view frame
let lonlat = radec;
if (aladin.getFrame() === "GAL") {
lonlat = CooConversion.ICRSToGalactic(radec)
}
let [lon, lat] = lonlat;
//self.field.el.blur()
self.update({
ra, dec,
frame,
center: true,
lon, lat,
frame: aladin.view.cooFrame,
isViewCenter: true,
}, aladin);
}
@@ -175,46 +170,39 @@ export class Location extends DOMElement {
self.update({
mouseX: param.xy.x,
mouseY: param.xy.y,
frame,
center: false,
frame: aladin.view.cooFrame,
isViewCenter: false,
}, aladin);
}
});
ALEvent.POSITION_CHANGED.listenedBy(aladin.aladinDiv, function (e) {
// center position in ICRS
let {ra, dec} = e.detail;
let frame = aladin.getFrame();
self.update({
ra,
dec,
center: true,
frame
lon: e.detail.lon,
lat: e.detail.lat,
isViewCenter: true,
frame: aladin.view.cooFrame
}, aladin);
});
ALEvent.FRAME_CHANGED.listenedBy(aladin.aladinDiv, function (e) {
let [ra, dec] = aladin.getRaDec();
let frame = aladin.getFrame();
let [lon, lat] = aladin.getRaDec();
self.update({
ra, dec,
center: true,
frame
lon, lat,
isViewCenter: true,
frame: e.detail.cooFrame
}, aladin);
});
this.aladin = aladin;
let [ra, dec] = aladin.getRaDec();
let frame = aladin.getFrame();
let [lon, lat] = aladin.getRaDec();
this.update({
ra,
dec,
frame,
center: true
lon, lat,
isViewCenter: true,
frame: aladin.view.cooFrame
}, aladin)
};
@@ -222,7 +210,6 @@ export class Location extends DOMElement {
update(options, aladin) {
let self = this;
// lon and lat must be given in cooFrame
const updateFromLonLatFunc = (lon, lat, cooFrame) => {
var coo = new Coo(lon, lat, Location.prec);
if (cooFrame == CooFrameEnum.ICRS) {
@@ -237,21 +224,21 @@ export class Location extends DOMElement {
self.field.removeClass('aladin-not-valid');
self.field.removeClass('aladin-valid');
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'white';
self.field.element().style.color = options.isViewCenter ? 'var(--aladin-color)' : 'white';
//self.field.el.blur()
};
if (options.ra && options.dec) {
let [lon, lat] = radec2Lonlat([options.ra, options.dec], options.frame)
updateFromLonLatFunc(lon, lat, options.frame);
if (options.lon && options.lat) {
updateFromLonLatFunc(options.lon, options.lat, options.frame, true);
} else if (options.mouseX && options.mouseY) {
try {
let lonlat = aladin.pix2world(options.mouseX, options.mouseY); // This is given in the frame of the view
if (lonlat) {
if (lonlat[0] < 0) {
lonlat = [lonlat[0] + 360.0, lonlat[1]];
let radec = aladin.pix2world(options.mouseX, options.mouseY); // This is given in the frame of the view
if (radec) {
if (radec[0] < 0) {
radec = [radec[0] + 360.0, radec[1]];
}
updateFromLonLatFunc(lonlat[0], lonlat[1], options.frame);
updateFromLonLatFunc(radec[0], radec[1], options.frame, false);
}
} catch(e) {}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2023 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { SelectorButton } from "../Widgets/Selector";
/******************************************************************************
* Aladin Lite project
*
* File gui/Form.js
*
* A context menu that shows when the user right clicks, or long touch on touch device
*
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
/*
Exemple of layout object
{
{
label: "ID",
type: "text",
value: "the placeholder value...",
},
*/
/*
options = {cmap1: {imgUrl, change}, cmap2: imgUrl, selected: cmap1}
*/
export class CmapSelector extends SelectorButton {
/**
* Create a layout
* @param {{layout: {type: String, name: String, value: Number | String, placeholder: Number | String, change: Function } | {type: String, name: String, checked: Boolean, change: Function } | { type: String, name: String, value: String, options: Array.<String>, change: Function }, cssStyle: Object}} options - Represents the structure of the Tabs
* @param {DOMElement} target - The parent element.
* @param {String} position - The position of the tabs layout relative to the target.
* For the list of possibilities, see https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
*/
constructor(options, aladin, target, position = 'beforeend') {
for (const cmap in options) {
if (cmap === 'selected') {
continue;
}
options[cmap] = {
...options[cmap],
cssStyle: {
//border: 'none',
//borderRadius: '0',
//backgroundColor: 'black',
color: 'black',
width: '4rem',
overflow: 'hidden',
'font-family': 'monospace',
},
content: cmap,
tooltip: {content: cmap, position: {direction: 'left'}},
}
}
super(options, aladin, target, position)
}
}

View File

@@ -317,24 +317,15 @@ export let Circle = (function() {
};
Circle.prototype.isInStroke = function(ctx, view, x, y) {
if (!this.draw(ctx, view, true)) {
return false;
}
this.draw(ctx, view, true);
return ctx.isPointInStroke(x, y);
};
// From StackOverflow: https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
Circle.prototype.intersectsBBox = function(x, y, w, h, view) {
var centerXyview = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
if (!centerXyview) {
return false;
}
// compute the absolute distance between the middle of the bbox
// and the center of the circle
Circle.prototype.intersectsBBox = function(x, y, w, h) {
const circleDistance = {
x: Math.abs(centerXyview[0] - (x + w/2)),
y: Math.abs(centerXyview[1] - (y + h/2))
x: Math.abs(this.center.x - x),
y: Math.abs(this.center.y - y)
};
if (circleDistance.x > (w/2 + this.radius)) { return false; }

View File

@@ -291,7 +291,6 @@ export let Ellipse = (function() {
ctx.globalAlpha = this.opacity;
ctx.beginPath();
this.aPixels = px_per_deg * this.a;
ctx.ellipse(originScreen[0], originScreen[1], px_per_deg * this.a, px_per_deg * this.b, theta, 0, 2*Math.PI, false);
if (!noStroke) {
if (this.fillColor) {
@@ -338,38 +337,15 @@ export let Ellipse = (function() {
};
Ellipse.prototype.isInStroke = function(ctx, view, x, y) {
if (!this.draw(ctx, view, true)) {
if (!this.draw(ctx, view, true, true)) {
return false;
}
return ctx.isPointInStroke(x, y);
};
Ellipse.prototype.intersectsBBox = function(x, y, w, h, view) {
// TODO: currently the same as Circle where radius = a.
var centerXyview = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
if (!centerXyview) {
return false;
}
// compute the absolute distance between the middle of the bbox
// and the center of the circle
const circleDistance = {
x: Math.abs(centerXyview[0] - (x + w/2)),
y: Math.abs(centerXyview[1] - (y + h/2))
};
if (circleDistance.x > (w/2 + this.aPixels)) { return false; }
if (circleDistance.y > (h/2 + this.aPixels)) { return false; }
if (circleDistance.x <= (w/2)) { return true; }
if (circleDistance.y <= (h/2)) { return true; }
const dx = circleDistance.x - w/2;
const dy = circleDistance.y - h/2;
const cornerDistanceSquared = dx*dx + dy*dy;
return (cornerDistanceSquared <= (this.aPixels*this.aPixels));
Ellipse.prototype.intersectsBBox = function(x, y, w, h) {
// todo
};
return Ellipse;

View File

@@ -232,7 +232,6 @@ export let Polyline = (function() {
return false;
}
noSmallCheck = noSmallCheck===true || false;
noStroke = noStroke===true || false;
@@ -270,28 +269,19 @@ export let Polyline = (function() {
let ymin = Number.POSITIVE_INFINITY
let ymax = Number.NEGATIVE_INFINITY;
let behind = true;
for (var k=0; k<len; k++) {
var xyview = view.aladin.world2pix(this.raDecArray[k][0], this.raDecArray[k][1]);
if (!xyview) {
xyView.push(undefined);
} else {
behind = false;
let [x, y] = xyview
xyView.push({x, y});
xmin = Math.min(xmin, x);
ymin = Math.min(ymin, y);
xmax = Math.max(xmax, x);
ymax = Math.max(ymax, y);
return false;
}
}
if (behind)
return false;
xyView.push({x: xyview[0], y: xyview[1]});
xmin = Math.min(xmin, xyview[0]);
ymin = Math.min(ymin, xyview[1]);
xmax = Math.max(xmax, xyview[0]);
ymax = Math.max(ymax, xyview[1]);
}
// 2. do not draw the polygon if it lies outside the view
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
@@ -300,7 +290,7 @@ export let Polyline = (function() {
// do not draw neither if the polygone does not lie inside lineWidth
if (!noSmallCheck) {
this.isTooSmall = (xmax - xmin) < this.lineWidth && (ymax - ymin) < this.lineWidth;
this.isTooSmall = (xmax - xmin) < this.lineWidth || (ymax - ymin) < this.lineWidth;
if (this.isTooSmall) {
return false;
@@ -312,10 +302,6 @@ export let Polyline = (function() {
if (view.projection === ProjectionEnum.SIN) {
drawLine = (v0, v1) => {
if (v0 === undefined || v1 === undefined) {
return false;
}
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (Polyline.isInsideView(l.x1, l.y1, l.x2, l.y2, view.width, view.height)) {
@@ -325,9 +311,6 @@ export let Polyline = (function() {
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
if (v0 === undefined || v1 === undefined)
return false;
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (index === 0) {
@@ -408,6 +391,7 @@ export let Polyline = (function() {
v1 = v1 + 1;
}
//ctx.globalAlpha = 1;
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.globalAlpha = this.opacity;
@@ -425,41 +409,30 @@ export let Polyline = (function() {
for (var j = 0; j < this.raDecArray.length; j++) {
var xy = view.aladin.world2pix(this.raDecArray[j][0], this.raDecArray[j][1]);
if (!xy) {
pointXY.push(undefined)
} else {
pointXY.push({
x: xy[0],
y: xy[1]
});
return false;
}
pointXY.push({
x: xy[0],
y: xy[1]
});
}
const lastPointIdx = pointXY.length - 1;
for (var l = 0; l < lastPointIdx; l++) {
let v1 = pointXY[l];
let v2 = pointXY[l + 1];
const line = {x1: pointXY[l].x, y1: pointXY[l].y, x2: pointXY[l + 1].x, y2: pointXY[l + 1].y}; // new segment
_drawLine(line, ctx, true);
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x, y is on line?
return true;
}
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
}
}
if(this.closed) {
let v1 = pointXY[lastPointIdx];
let v2 = pointXY[0];
const line = {x1: pointXY[lastPointIdx].x, y1: pointXY[lastPointIdx].y, x2: pointXY[0].x, y2: pointXY[0].y}; // new segment
_drawLine(line, ctx, true);
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
}
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
}
}

View File

@@ -175,29 +175,8 @@ export let Vector = (function() {
isInStroke: Ellipse.prototype.isInStroke,
lineIntersectsBox: Polyline.prototype.lineIntersectsBox,
intersectsBBox: function(x, y, w, h, view) {
let p1 = [this.ra1, this.dec1];
let p2 = [this.ra2, this.dec2];
let xy1 = view.aladin.world2pix(p1[0], p1[1]);
let xy2 = view.aladin.world2pix(p2[0], p2[1]);
if (!xy1 || !xy2) {
return false;
}
xy1 = {x: xy1[0], y: xy1[1]};
xy2 = {x: xy2[0], y: xy2[1]};
// Check if line segment intersects with the bounding box
if (this.lineIntersectsBox(xy1, xy2, x, y, w, h)) {
return true;
}
return false;
intersectsBBox: function(x, y, w, h) {
// todo
},
};

View File

@@ -72,7 +72,7 @@ export class SAMPConnector {
var params = message["samp.params"];
const {url, name} = params;
let moc = A.MOCFromURL(url, {name});
let moc = A.MOCFromURL(url, {name, lineWidth: 3});
aladin.addMOC(moc);
};