mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-12 15:49:18 -08:00
feat: add possibility of loading a local hips without the need of starting an http server
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
aladin = A.aladin(
|
||||
'#aladin-lite-div',
|
||||
{
|
||||
showSimbadPointerControl: true,
|
||||
survey: 'P/allWISE/color', // set initial image survey
|
||||
projection: 'AIT', // set a projection
|
||||
fov: 1.5, // initial field of view in degrees
|
||||
@@ -30,20 +31,14 @@
|
||||
}
|
||||
);
|
||||
|
||||
aladin.on("positionChanged", ({ra, dec, dragging}) => {
|
||||
console.log('position changed dragging: ', dragging)
|
||||
});
|
||||
|
||||
let id;
|
||||
/*let id;
|
||||
aladin.on("zoomChanged", () => {
|
||||
if (id)
|
||||
clearTimeout(id);
|
||||
id = setTimeout(() => {
|
||||
console.log("wheel stopped, new cone search here")
|
||||
}, 500);
|
||||
})
|
||||
|
||||
//aladin.removeHiPSFromFavorites('CDS/P/allWISE/color')
|
||||
})*/
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@@ -70,7 +70,7 @@ path = "./al-api"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "*"
|
||||
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", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream",]
|
||||
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", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList"]
|
||||
|
||||
[dev-dependencies.image-decoder]
|
||||
package = "image"
|
||||
|
||||
@@ -61,6 +61,8 @@ features = [
|
||||
'PerformanceTiming',
|
||||
'Performance',
|
||||
'Url',
|
||||
'File',
|
||||
'FileList'
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
||||
@@ -73,6 +73,8 @@ features = [
|
||||
'PerformanceTiming',
|
||||
'Performance',
|
||||
'Url',
|
||||
'File',
|
||||
'FileList'
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::renderable::ImageLayer;
|
||||
use crate::tile_fetcher::HiPSLocalFiles;
|
||||
use crate::{
|
||||
//async_task::{BuildCatalogIndex, ParseTableTask, TaskExecutor, TaskResult, TaskType},
|
||||
camera::CameraViewPort,
|
||||
@@ -302,16 +303,6 @@ impl App {
|
||||
ancestors.insert(ancestor_tile_cell);
|
||||
}
|
||||
}
|
||||
//let ancestor_next_tile_cell = next_tile_cell.ancestor(3);
|
||||
//if !survey.contains_tile(&ancestor_tile_cell) {
|
||||
//self.tile_fetcher.append(
|
||||
// query::Tile::new(&ancestor_tile_cell, hips_url.clone(), format),
|
||||
// &mut self.downloader,
|
||||
//);
|
||||
//}
|
||||
//if ancestor_tile_cell != ancestor_next_tile_cell {
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
// Request for ancestor
|
||||
@@ -805,8 +796,12 @@ impl App {
|
||||
// Check for async retrieval
|
||||
if let Ok(img) = self.img_recv.try_recv() {
|
||||
let params = img.get_params();
|
||||
self.layers
|
||||
.add_image(img, &mut self.camera, &self.projection)?;
|
||||
self.layers.add_image(
|
||||
img,
|
||||
&mut self.camera,
|
||||
&self.projection,
|
||||
&mut self.tile_fetcher,
|
||||
)?;
|
||||
self.request_redraw = true;
|
||||
|
||||
// Send the ack to the js promise so that she finished
|
||||
@@ -958,8 +953,12 @@ impl App {
|
||||
}
|
||||
|
||||
pub(crate) fn remove_layer(&mut self, layer: &str) -> Result<(), JsValue> {
|
||||
self.layers
|
||||
.remove_layer(layer, &mut self.camera, &self.projection)?;
|
||||
self.layers.remove_layer(
|
||||
layer,
|
||||
&mut self.camera,
|
||||
&self.projection,
|
||||
&mut self.tile_fetcher,
|
||||
)?;
|
||||
|
||||
self.request_redraw = true;
|
||||
|
||||
@@ -982,17 +981,31 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn add_image_hips(&mut self, hips_cfg: HiPSCfg) -> Result<(), JsValue> {
|
||||
let hips =
|
||||
self.layers
|
||||
.add_image_hips(&self.gl, hips_cfg, &mut self.camera, &self.projection)?;
|
||||
pub(crate) fn add_image_hips(
|
||||
&mut self,
|
||||
hips_cfg: HiPSCfg,
|
||||
local_files: Option<HiPSLocalFiles>,
|
||||
) -> Result<(), JsValue> {
|
||||
let cdid = hips_cfg.properties.get_creator_did().to_string();
|
||||
|
||||
let hips = self.layers.add_image_hips(
|
||||
&self.gl,
|
||||
hips_cfg,
|
||||
&mut self.camera,
|
||||
&self.projection,
|
||||
&mut self.tile_fetcher,
|
||||
)?;
|
||||
|
||||
if let Some(local_files) = local_files {
|
||||
self.tile_fetcher.insert_hips_local_files(cdid, local_files);
|
||||
}
|
||||
|
||||
self.tile_fetcher
|
||||
.launch_starting_hips_requests(hips, self.downloader.clone());
|
||||
|
||||
// Once its added, request the tiles in the view (unless the viewer is at depth 0)
|
||||
self.request_for_new_tiles = true;
|
||||
self.request_redraw = true;
|
||||
//self.grid.update(&self.camera, &self.projection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ use math::projection::*;
|
||||
|
||||
use moclib::moc::RangeMOCIntoIterator;
|
||||
//use votable::votable::VOTableWrapper;
|
||||
use crate::tile_fetcher::HiPSLocalFiles;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
@@ -160,11 +161,7 @@ impl WebClient {
|
||||
/// * `shaders` - The list of shader objects containing the GLSL code source
|
||||
/// * `resources` - Image resource files
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
aladin_div: &HtmlElement,
|
||||
//_shaders: JsValue,
|
||||
resources: JsValue,
|
||||
) -> Result<WebClient, JsValue> {
|
||||
pub fn new(aladin_div: &HtmlElement, resources: JsValue) -> Result<WebClient, JsValue> {
|
||||
#[cfg(feature = "dbg")]
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
@@ -371,10 +368,14 @@ impl WebClient {
|
||||
/// of WebGL2 texture units on some architectures, the total number of surveys rendered is
|
||||
/// limited to 4.
|
||||
#[wasm_bindgen(js_name = addHiPS)]
|
||||
pub fn add_image_hips(&mut self, hips: JsValue) -> Result<(), JsValue> {
|
||||
pub fn add_image_hips(
|
||||
&mut self,
|
||||
hips: JsValue,
|
||||
files: Option<HiPSLocalFiles>,
|
||||
) -> Result<(), JsValue> {
|
||||
// Deserialize the survey objects that compose the survey
|
||||
let hips = serde_wasm_bindgen::from_value(hips)?;
|
||||
self.app.add_image_hips(hips)?;
|
||||
self.app.add_image_hips(hips, files)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod text;
|
||||
pub mod utils;
|
||||
|
||||
use crate::renderable::image::Image;
|
||||
use crate::tile_fetcher::TileFetcherQueue;
|
||||
|
||||
use al_core::image::format::ChannelType;
|
||||
|
||||
@@ -316,6 +317,7 @@ impl Layers {
|
||||
layer: &str,
|
||||
camera: &mut CameraViewPort,
|
||||
proj: &ProjectionType,
|
||||
tile_fetcher: &mut TileFetcherQueue,
|
||||
) -> Result<usize, JsValue> {
|
||||
let err_layer_not_found = JsValue::from_str(&format!(
|
||||
"Layer {:?} not found, so cannot be removed.",
|
||||
@@ -351,6 +353,9 @@ impl Layers {
|
||||
// remove the frame
|
||||
camera.unregister_view_frame(hips_frame, proj);
|
||||
|
||||
// remove the local files access from the tile fetcher
|
||||
tile_fetcher.delete_hips_local_files(s.get_config().get_creator_did());
|
||||
|
||||
Ok(id_layer)
|
||||
} else if let Some(_) = self.images.remove(&id) {
|
||||
// A FITS image has been found and removed
|
||||
@@ -418,6 +423,7 @@ impl Layers {
|
||||
hips: HiPSCfg,
|
||||
camera: &mut CameraViewPort,
|
||||
proj: &ProjectionType,
|
||||
tile_fetcher: &mut TileFetcherQueue,
|
||||
) -> Result<&HiPS, JsValue> {
|
||||
let HiPSCfg {
|
||||
layer,
|
||||
@@ -431,7 +437,7 @@ impl Layers {
|
||||
let layer_already_found = self.layers.iter().any(|l| l == &layer);
|
||||
|
||||
let idx = if layer_already_found {
|
||||
let idx = self.remove_layer(&layer, camera, proj)?;
|
||||
let idx = self.remove_layer(&layer, camera, proj, tile_fetcher)?;
|
||||
idx
|
||||
} else {
|
||||
self.layers.len()
|
||||
@@ -492,6 +498,7 @@ impl Layers {
|
||||
image: ImageLayer,
|
||||
camera: &mut CameraViewPort,
|
||||
proj: &ProjectionType,
|
||||
tile_fetcher: &mut TileFetcherQueue,
|
||||
) -> Result<&[Image], JsValue> {
|
||||
let ImageLayer {
|
||||
layer,
|
||||
@@ -504,7 +511,7 @@ impl Layers {
|
||||
let layer_already_found = self.layers.iter().any(|s| s == &layer);
|
||||
|
||||
let idx = if layer_already_found {
|
||||
let idx = self.remove_layer(&layer, camera, proj)?;
|
||||
let idx = self.remove_layer(&layer, camera, proj, tile_fetcher)?;
|
||||
idx
|
||||
} else {
|
||||
self.layers.len()
|
||||
|
||||
@@ -27,6 +27,7 @@ impl<const N: usize> BitVector<N> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BitVector;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::time::{DeltaTime, Time};
|
||||
use crate::Abort;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::rc::Rc;
|
||||
|
||||
const MAX_NUM_TILE_FETCHING: usize = 8;
|
||||
@@ -16,25 +16,68 @@ pub struct TileFetcherQueue {
|
||||
base_tile_queries: Vec<query::Tile>,
|
||||
tiles_fetched_time: Time,
|
||||
num_tiles_fetched: usize,
|
||||
|
||||
hips_local_files: HashMap<CreatorDid, HiPSLocalFiles>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen]
|
||||
pub struct HiPSLocalFiles {
|
||||
paths: Box<[HashMap<u64, web_sys::File>]>,
|
||||
}
|
||||
|
||||
use crate::tile_fetcher::query::Tile;
|
||||
use crate::HEALPixCell;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HiPSLocalFiles {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
let paths = vec![HashMap::new(); 30].into_boxed_slice();
|
||||
|
||||
Self { paths }
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, depth: u8, ipix: u64, file: web_sys::File) {
|
||||
self.paths[depth as usize].insert(ipix, file);
|
||||
}
|
||||
|
||||
fn get(&self, cell: &HEALPixCell) -> Option<&web_sys::File> {
|
||||
let d = cell.depth() as usize;
|
||||
let i = cell.idx();
|
||||
|
||||
return self.paths[d].get(&i);
|
||||
}
|
||||
}
|
||||
use crate::renderable::CreatorDid;
|
||||
impl TileFetcherQueue {
|
||||
pub fn new() -> Self {
|
||||
let queries = VecDeque::new();
|
||||
let base_tile_queries = Vec::new();
|
||||
let tiles_fetched_time = Time::now();
|
||||
let num_tiles_fetched = 0;
|
||||
|
||||
Self {
|
||||
queries,
|
||||
base_tile_queries,
|
||||
tiles_fetched_time,
|
||||
num_tiles_fetched,
|
||||
hips_local_files: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_hips_local_files(&mut self, id: CreatorDid, local_files: HiPSLocalFiles) {
|
||||
self.hips_local_files.insert(id, local_files);
|
||||
}
|
||||
|
||||
pub fn delete_hips_local_files(&mut self, id: &str) {
|
||||
self.hips_local_files.remove(id);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.queries.clear();
|
||||
//self.query_set.clear();
|
||||
}
|
||||
|
||||
pub fn append(&mut self, query: query::Tile) {
|
||||
@@ -72,23 +115,41 @@ impl TileFetcherQueue {
|
||||
self.num_tiles_fetched
|
||||
}
|
||||
|
||||
fn check_in_file_list(&self, mut query: Tile) -> Result<Tile, JsValue> {
|
||||
if let Some(files) = self.hips_local_files.get(&query.hips_cdid) {
|
||||
if let Some(file) = files.get(&query.cell) {
|
||||
if let Ok(url) = web_sys::Url::create_object_url_with_blob(file.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)
|
||||
}
|
||||
} else {
|
||||
Ok(query)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch(&mut self, downloader: Rc<RefCell<Downloader>>) {
|
||||
// Fetch the base tiles with higher priority
|
||||
while let Some(query) = self.base_tile_queries.pop() {
|
||||
//if downloader.fetch(query) {
|
||||
// The fetch has succeded
|
||||
//self.num_tiles_fetched += 1;
|
||||
//}
|
||||
downloader.borrow_mut().fetch(query);
|
||||
if let Ok(query) = self.check_in_file_list(query) {
|
||||
downloader.borrow_mut().fetch(query);
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_fetched_tile = 0;
|
||||
while num_fetched_tile < MAX_NUM_TILE_FETCHING && !self.queries.is_empty() {
|
||||
let query = self.queries.pop_back().unwrap_abort();
|
||||
|
||||
if downloader.borrow_mut().fetch(query) {
|
||||
// The fetch has succeded
|
||||
num_fetched_tile += 1;
|
||||
if let Ok(query) = self.check_in_file_list(query) {
|
||||
if downloader.borrow_mut().fetch(query) {
|
||||
// The fetch has succeded
|
||||
num_fetched_tile += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,20 +185,23 @@ impl TileFetcherQueue {
|
||||
let hips_fmt = cfg.get_format();
|
||||
let min_order = cfg.get_min_depth_texture();
|
||||
|
||||
let dl = downloader.clone();
|
||||
crate::utils::set_timeout(
|
||||
move || {
|
||||
for tile_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
|
||||
dl.borrow_mut().fetch(query::Tile::new(
|
||||
tile_cell,
|
||||
hips_cdid.clone(),
|
||||
hips_url.clone(),
|
||||
hips_fmt,
|
||||
));
|
||||
}
|
||||
},
|
||||
2_000,
|
||||
);
|
||||
for tile_cell in crate::healpix::cell::ALLSKY_HPX_CELLS_D0 {
|
||||
if let Ok(query) = self.check_in_file_list(query::Tile::new(
|
||||
tile_cell,
|
||||
hips_cdid.clone(),
|
||||
hips_url.clone(),
|
||||
hips_fmt,
|
||||
)) {
|
||||
let dl = downloader.clone();
|
||||
|
||||
crate::utils::set_timeout(
|
||||
move || {
|
||||
dl.borrow_mut().fetch(query);
|
||||
},
|
||||
2_000,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
src/js/A.js
25
src/js/A.js
@@ -113,17 +113,7 @@ A.aladin = function (divSelector, options) {
|
||||
* @deprecated
|
||||
* Old method name, use {@link A.HiPS} instead.
|
||||
*/
|
||||
A.imageHiPS = function (id, options) {
|
||||
let url = id;
|
||||
return Aladin.createImageSurvey(
|
||||
id,
|
||||
options && options.name,
|
||||
url,
|
||||
options && options.cooFrame,
|
||||
options && options.maxOrder,
|
||||
options
|
||||
);
|
||||
}
|
||||
A.imageHiPS = A.HiPS;
|
||||
|
||||
/**
|
||||
* Creates a HiPS image object
|
||||
@@ -136,7 +126,17 @@ A.imageHiPS = function (id, options) {
|
||||
* @param {HiPSOptions} [options] - Options describing the survey
|
||||
* @returns {HiPS} - A HiPS image object
|
||||
*/
|
||||
A.HiPS = A.imageHiPS;
|
||||
A.HiPS = function (id, options) {
|
||||
let url = id;
|
||||
return Aladin.createImageSurvey(
|
||||
id,
|
||||
options && options.name,
|
||||
url,
|
||||
options && options.cooFrame,
|
||||
options && options.maxOrder,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a celestial source object with the given coordinates.
|
||||
@@ -818,6 +818,7 @@ A.catalogFromSKAORucio = function (target, radiusDegrees, options, successCallba
|
||||
*
|
||||
* @example
|
||||
* const cat = A.catalogFromVizieR('I/311/hip2', 'M 45', 5, {onClick: 'showTable'});
|
||||
* const cat2 = A.catalogFromVizieR('I/311/hip2', '12 +9', 5, {onClick: 'showTable'});
|
||||
*/
|
||||
A.catalogFromVizieR = function (vizCatId, target, radius, options, successCallback, errorCallback) {
|
||||
options = options || {};
|
||||
|
||||
@@ -1471,10 +1471,10 @@ export let Aladin = (function () {
|
||||
* @memberof Aladin
|
||||
*/
|
||||
Aladin.prototype.removeLayers = Aladin.prototype.removeOverlays;
|
||||
/**
|
||||
* @typedef {MOC|Catalog|ProgressiveCat|GraphicOverlay} Overlay
|
||||
* @description Possible overlays
|
||||
*/
|
||||
/**
|
||||
* @typedef {MOC|Catalog|ProgressiveCat|GraphicOverlay} Overlay
|
||||
* @description Possible overlays
|
||||
*/
|
||||
/**
|
||||
* Remove an overlay by its layer name
|
||||
*
|
||||
@@ -1667,7 +1667,8 @@ 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,
|
||||
return this.createImageSurvey(
|
||||
id,
|
||||
options && options.name,
|
||||
id,
|
||||
options && options.cooFrame,
|
||||
|
||||
@@ -47,7 +47,7 @@ import { Footprint } from "./Footprint.js";
|
||||
* @property {string} [name="catalog"] - The name of the catalog.
|
||||
* @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} [shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
|
||||
* @property {string|Function|Image|HTMLCanvasElement|HTMLImageElement} [shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
|
||||
* @property {number} [limit] - The maximum number of sources to display.
|
||||
* @property {string|Function} [onClick] - Whether the source data appears as a table row or a in popup. Can be 'showTable' string, 'showPopup' string or a custom user defined function that handles the click.
|
||||
* @property {boolean} [readOnly=false] - Whether the catalog is read-only.
|
||||
@@ -479,7 +479,17 @@ export let Catalog = (function () {
|
||||
);
|
||||
};
|
||||
|
||||
// API
|
||||
/**
|
||||
* Set the shape of the sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
* @param {Object} [options] - shape options
|
||||
* @param {string} [options.color] - the color of the shape
|
||||
* @param {number} [options.sourceSize] - size of the shape
|
||||
* @param {string|Function|HTMLImageCanvas|HTMLImageElement} [options.shape="square"] - the type of the shape. Can be square, rhomb, plus, cross, triangle, circle.
|
||||
* A callback function can also be called that return an HTMLImageElement in function of the source object. A canvas or an image can also be given.
|
||||
*/
|
||||
Catalog.prototype.updateShape = function (options) {
|
||||
options = options || {};
|
||||
this.color = options.color || this.color || Color.getNextColor();
|
||||
@@ -524,7 +534,13 @@ export let Catalog = (function () {
|
||||
this.reportChange();
|
||||
};
|
||||
|
||||
// API
|
||||
/**
|
||||
* Add sources to the catalog
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
* @param {Source[]} sources - An array of sources or only one source to add
|
||||
*/
|
||||
Catalog.prototype.addSources = function (sources) {
|
||||
// make sure we have an array and not an individual source
|
||||
sources = [].concat(sources);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import { ALEvent } from "./events/ALEvent.js";
|
||||
import { ColorCfg } from "./ColorCfg.js";
|
||||
import { HiPSProperties } from "./HiPSProperties.js";
|
||||
|
||||
import { Aladin } from "./Aladin.js";
|
||||
let PropertyParser = {};
|
||||
// Utilitary functions for parsing the properties and giving default values
|
||||
/// Mandatory tileSize property
|
||||
@@ -161,10 +161,11 @@ export let HiPS = (function () {
|
||||
* @constructs HiPS
|
||||
*
|
||||
* @param {string} id - Mandatory unique identifier for the layer. Can be an arbitrary name
|
||||
* @param {string} location - Can be:
|
||||
* @param {string|FileList|Object} location - Can be:
|
||||
* - an http url <br/>
|
||||
* - a relative path to your HiPS <br/>
|
||||
* - a special ID pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here}
|
||||
* - a dict storing a local HiPS. This object contains a tile file: hips[order][ipix] = <tile File> and refers to the properties file like so: hips["properties"] = <properties File>. A javascript FileList pointing to the opened webkit directory is also accepted.
|
||||
* @param {HiPSOptions} [options] - The option for the survey
|
||||
*
|
||||
* @description Giving a CDS ID will do a query to the MOCServer first to retrieve metadata. Then it will also check for the presence of faster HiPS nodes to choose a faster url to query to tiles from.
|
||||
@@ -178,6 +179,30 @@ export let HiPS = (function () {
|
||||
this.name = (options && options.name) || undefined;
|
||||
this.startUrl = options.startUrl;
|
||||
|
||||
if (location instanceof FileList) {
|
||||
let files = {};
|
||||
for (var file of location) {
|
||||
let path = file.webkitRelativePath;
|
||||
if (path.includes("Norder") && path.includes("Npix")) {
|
||||
const order = +path.substring(path.indexOf("Norder") + 6).split("/")[0];
|
||||
if (!files[order]) {
|
||||
files[order] = {}
|
||||
}
|
||||
|
||||
const ipix = +path.substring(path.indexOf("Npix") + 4).split(".")[0];
|
||||
files[order][ipix] = file;
|
||||
}
|
||||
|
||||
if (path.includes("properties")) {
|
||||
files['properties'] = file;
|
||||
}
|
||||
}
|
||||
|
||||
this.files = files;
|
||||
} else if (location instanceof Object) {
|
||||
this.files = location;
|
||||
}
|
||||
|
||||
this.url = location;
|
||||
this.maxOrder = options.maxOrder;
|
||||
this.minOrder = options.minOrder || 0;
|
||||
@@ -402,11 +427,30 @@ export let HiPS = (function () {
|
||||
}
|
||||
this.view = view;
|
||||
|
||||
let isIncompleteOptions = true;
|
||||
// This is very dirty but it allows me to differentiate the location from
|
||||
// whether an ID or a plain url
|
||||
let isID = this.url.includes("P/") || this.url.includes("C/")
|
||||
if (this.files) {
|
||||
// Fetch the properties file
|
||||
self.query = (async () => {
|
||||
// look for the properties file
|
||||
await HiPSProperties.fetchFromFile(self.files["properties"])
|
||||
.then((p) => {
|
||||
self._parseProperties(p);
|
||||
|
||||
self.url = "local";
|
||||
|
||||
delete self.files["properties"]
|
||||
})
|
||||
|
||||
return self;
|
||||
})();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let isIncompleteOptions = true;
|
||||
|
||||
// This is very dirty but it allows me to differentiate the location from whether it is an ID or a plain url
|
||||
let isID = this.url.includes("P/") || this.url.includes("C/")
|
||||
|
||||
if (this.imgFormat === "fits") {
|
||||
// a fits is given
|
||||
isIncompleteOptions = !(
|
||||
@@ -428,7 +472,6 @@ export let HiPS = (function () {
|
||||
}
|
||||
|
||||
self.query = (async () => {
|
||||
|
||||
if (isIncompleteOptions) {
|
||||
// ID typed url
|
||||
if (self.startUrl && isID) {
|
||||
@@ -459,8 +502,6 @@ export let HiPS = (function () {
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
@@ -799,7 +840,7 @@ export let HiPS = (function () {
|
||||
this.layer = layer;
|
||||
let self = this;
|
||||
|
||||
this.view.wasm.addHiPS({
|
||||
const config = {
|
||||
layer,
|
||||
properties: {
|
||||
creatorDid: self.creatorDid,
|
||||
@@ -821,8 +862,24 @@ export let HiPS = (function () {
|
||||
...this.colorCfg.get(),
|
||||
longitudeReversed: this.longitudeReversed,
|
||||
imgFormat: this.imgFormat,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let localFiles;
|
||||
if (this.files) {
|
||||
localFiles = new Aladin.wasmLibs.core.HiPSLocalFiles();
|
||||
for (var order in this.files) {
|
||||
for (var ipix in this.files[order]) {
|
||||
const file = this.files[order][ipix];
|
||||
localFiles.insert(+order, BigInt(+ipix), file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.view.wasm.addHiPS(
|
||||
config,
|
||||
localFiles
|
||||
);
|
||||
|
||||
return Promise.resolve(this)
|
||||
.then((hips) => {
|
||||
|
||||
@@ -70,6 +70,8 @@ HiPSProperties.fetchFromID = async function(ID) {
|
||||
}
|
||||
|
||||
HiPSProperties.fetchFromUrl = async function(urlOrId) {
|
||||
let url;
|
||||
|
||||
try {
|
||||
urlOrId = new URL(urlOrId);
|
||||
} catch (e) {
|
||||
@@ -85,7 +87,7 @@ HiPSProperties.fetchFromUrl = async function(urlOrId) {
|
||||
// Fetch the properties of the survey
|
||||
const HiPSServiceUrl = urlOrId.toString();
|
||||
|
||||
let url = HiPSServiceUrl;
|
||||
url = HiPSServiceUrl;
|
||||
// Use the url for retrieving the HiPS properties
|
||||
// remove final slash
|
||||
if (url.slice(-1) === '/') {
|
||||
@@ -98,6 +100,7 @@ HiPSProperties.fetchFromUrl = async function(urlOrId) {
|
||||
// fix for HTTPS support --> will work for all HiPS served by CDS
|
||||
url = Utils.fixURLForHTTPS(url)
|
||||
|
||||
|
||||
let init = {};
|
||||
if (Utils.requestCORSIfNotSameOrigin(url)) {
|
||||
init = { mode: 'cors' };
|
||||
@@ -121,7 +124,9 @@ HiPSProperties.fetchFromUrl = async function(urlOrId) {
|
||||
if (!metadata.hips_frame || !metadata.hips_order) {
|
||||
reject('Bad properties: do not contain the mandatory frame or order info')
|
||||
} else {
|
||||
metadata.hips_service_url = HiPSServiceUrl;
|
||||
if (!("hips_service_url" in metadata)) {
|
||||
metadata.hips_service_url = HiPSServiceUrl;
|
||||
}
|
||||
resolve(metadata);
|
||||
}
|
||||
} else {
|
||||
@@ -133,6 +138,27 @@ HiPSProperties.fetchFromUrl = async function(urlOrId) {
|
||||
return result;
|
||||
}
|
||||
|
||||
HiPSProperties.fetchFromFile = function(file) {
|
||||
let url = URL.createObjectURL(file);
|
||||
return fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then(
|
||||
(response) => new Promise((resolve, reject) => {
|
||||
// We get the property here
|
||||
let metadata = HiPSDefinition.parseHiPSProperties(response);
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
// 1. Ensure there is exactly one survey matching
|
||||
if (metadata && Object.keys(metadata).length > 0) {
|
||||
resolve(metadata)
|
||||
} else {
|
||||
reject('No surveys matching at this url: ' + rootURL);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
HiPSProperties.getFasterMirrorUrl = function (metadata) {
|
||||
const pingHiPSServiceUrl = async (baseUrl) => {
|
||||
baseUrl = Utils.fixURLForHTTPS(baseUrl);
|
||||
|
||||
@@ -2088,12 +2088,12 @@ export let View = (function () {
|
||||
};
|
||||
|
||||
View.prototype.removeOverlayByName = function (overlayName) {
|
||||
let layer = this.allOverlayLayers.find(l => l.name === overlayName);
|
||||
if (!layer) {
|
||||
let overlay = this.allOverlayLayers.find(l => l.name === overlayName);
|
||||
if (!overlay) {
|
||||
console.error(`Layer with name "${overlayName}" not found.`);
|
||||
return;
|
||||
}
|
||||
this.removeOverlay(layer);
|
||||
this.removeOverlay(overlay);
|
||||
};
|
||||
|
||||
View.prototype.add = function(overlay) {
|
||||
|
||||
@@ -580,7 +580,8 @@ export class OverlayStackBox extends Box {
|
||||
// Center the view around the new fits object
|
||||
self.aladin.gotoRaDec(ra, dec);
|
||||
self.aladin.setFoV(fov * 1.1);
|
||||
//self.aladin.selectLayer(image.layer);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -590,6 +591,31 @@ export class OverlayStackBox extends Box {
|
||||
);
|
||||
},
|
||||
}),
|
||||
ContextMenu.webkitDir({
|
||||
label: "Load local HiPS",
|
||||
action(files) {
|
||||
let id = files[0].webkitRelativePath.split("/")[0];
|
||||
let name = id;
|
||||
|
||||
let hips = self.aladin.createImageSurvey(
|
||||
id,
|
||||
name,
|
||||
files,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
errorCallback: (e) => {
|
||||
aladin.addStatusBarMessage({
|
||||
duration: 2000,
|
||||
type: 'info',
|
||||
message: 'Could not add the local HiPS',
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
self.aladin.addNewImageLayer(hips);
|
||||
},
|
||||
}),
|
||||
],
|
||||
tooltip: {
|
||||
content: "Add a HiPS or an FITS image",
|
||||
|
||||
@@ -407,4 +407,36 @@ export class ContextMenu extends DOMElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static webkitDir(itemOptions) {
|
||||
return {
|
||||
...itemOptions,
|
||||
label: {
|
||||
icon: {
|
||||
monochrome: true,
|
||||
tooltip: {content: 'Load a local file from your computer.<br \>Accept ' + itemOptions.accept + ' files'},
|
||||
url: uploadIconUrl,
|
||||
cssStyle: {
|
||||
cursor: 'help',
|
||||
}
|
||||
},
|
||||
content: itemOptions.label
|
||||
},
|
||||
action(e) {
|
||||
let webkitDirLoader = document.createElement('input');
|
||||
webkitDirLoader.type = 'file';
|
||||
webkitDirLoader.webkitdirectory = true;
|
||||
webkitDirLoader.multiple = true;
|
||||
|
||||
// Case: The user is loading a FITS file
|
||||
webkitDirLoader.addEventListener("change", (e) => {
|
||||
if (itemOptions.action) {
|
||||
itemOptions.action(e.target.files)
|
||||
}
|
||||
});
|
||||
|
||||
webkitDirLoader.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user