mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-12 07:40:26 -08:00
For Catalog only: plot the sources when the footprints are too small
This commit is contained in:
committed by
Matthieu Baumann
parent
c797aec7f7
commit
fee97bed40
@@ -10,7 +10,7 @@
|
||||
import A from '../src/js/A.js';
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, gridOptions: {opacity: 0.5, color: 'rgba(255, 0, 0)'}, projection: "AIT", cooFrame: 'icrs', target: "stephan's quintet", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false});
|
||||
aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, projection: "AIT", cooFrame: 'icrs', target: "stephan's quintet", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false});
|
||||
|
||||
const chft = aladin.createImageSurvey('CFHT', "CFHT deep view of NGC7331 and Stephan's quintet u+g+r", "https://cds.unistra.fr/~derriere/PR_HiPS/2022_Duc/", null, null, {imgFormat: 'png'});
|
||||
const nircamJWST = aladin.createImageSurvey('Nircam', "Stephans Quintet NIRCam+MIRI", "http://alasky.cds.unistra.fr/JWST/CDS_P_JWST_Stephans-Quintet_NIRCam+MIRI/", null, null, {imgFormat: 'png', colormap: "viridis"});
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', cooFrame: 'galactic', fov: 360, fullScreen: true, showCooGrid: false, showReticle: false})
|
||||
aladin.gotoRaDec(79.9525321, -69.2742586)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
|
||||
<div id="aladin-lite-div" style="width: 1024px; height: 100%;"></div>
|
||||
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
@@ -19,7 +19,6 @@
|
||||
target: '19 24 51.556 +45 16 44.36', // initial target
|
||||
cooFrame: 'equatorial', // set galactic frame
|
||||
showCooGrid: true, // set the grid
|
||||
fullScreen: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// Start up Aladin Lite
|
||||
aladin = A.aladin('#aladin-lite-div', {
|
||||
target: "M31",
|
||||
fov: 2,
|
||||
fov: 89.78,
|
||||
showContextMenu: true,
|
||||
fullScreen: true,
|
||||
showSimbadPointerControl: true,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
selectionColor: 'white',
|
||||
// Footprint associated to sources
|
||||
shape: (s) => {
|
||||
|
||||
// Compute the mean of pm over the catalog sources
|
||||
if (!pmraMean || !pmdecMean) {
|
||||
pmraMean = 0, pmdecMean = 0;
|
||||
@@ -47,16 +48,20 @@
|
||||
pmdecMean /= numSources
|
||||
}
|
||||
|
||||
console.log("mean", pmraMean, pmdecMean)
|
||||
|
||||
let dra = +s.data.pmra - pmraMean;
|
||||
let ddec = +s.data.pmdec - pmdecMean;
|
||||
|
||||
let mag = Math.sqrt(dra * dra + ddec * ddec);
|
||||
// discard drawing a vector for big pm
|
||||
if (mag > 1) {
|
||||
|
||||
|
||||
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
|
||||
if (totalPmSquared > 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
let color = rainbowColorMap(mag * 2)
|
||||
let color = rainbowColorMap((totalPmSquared - 2.5) / 2)
|
||||
|
||||
return A.vector(
|
||||
s.ra,
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
let a = +s.data.size_maj;
|
||||
let b = +s.data.size_min;
|
||||
|
||||
if (a < 0.1 || b < 0.1)
|
||||
let galaxy = ['Seyfert', 'Gin', 'StarburstG', 'LINER', 'AGN', 'Galaxy'].some((n) => s.data.main_type.indexOf(n) >= 0)
|
||||
if (!galaxy)
|
||||
return;
|
||||
|
||||
let angle = +s.data.size_angle || 0.0;
|
||||
|
||||
@@ -29,7 +29,6 @@ A.init.then(() => {
|
||||
|
||||
aladin.addCatalog(A.catalogFromURL(vmc_cepheids, {onClick: 'showTable', sourceSize:14, color: '#fff080'}));
|
||||
|
||||
|
||||
|
||||
aladin.addCatalog(A.catalogFromURL(pessto, {onClick: 'showPopup', sourceSize:14, color: '#00f080'}));
|
||||
aladin.on('select', (objs) => {
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
<body>
|
||||
|
||||
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
|
||||
|
||||
<script> let aladin;
|
||||
</script>
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
var aladin;
|
||||
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin(
|
||||
'#aladin-lite-div',
|
||||
@@ -22,8 +21,9 @@
|
||||
reticleColor: '#00ff00', // change reticle color
|
||||
reticleSize: 40, // change reticle size
|
||||
gridOptions: {color: 'pink'},
|
||||
showCooGrid: true, // set the grid
|
||||
showCooGrid: false, // set the grid
|
||||
fullScreen: true,
|
||||
inertia: false,
|
||||
showStatusBar: false,
|
||||
showShareControl: true,
|
||||
showSettingsControl: true,
|
||||
|
||||
@@ -17,45 +17,34 @@ pub enum Data<'a> {
|
||||
I32(Cow<'a, [i32]>),
|
||||
F32(Cow<'a, [f32]>),
|
||||
}
|
||||
use fitsrs::{fits::Fits as FitsData, hdu::data::InMemData};
|
||||
use std::io::Cursor;
|
||||
use fitsrs::{
|
||||
hdu::data::InMemData,
|
||||
fits::Fits as FitsData,
|
||||
};
|
||||
|
||||
impl<'a> Fits<'a> {
|
||||
pub fn from_byte_slice(bytes_reader: &'a mut Cursor<&[u8]>) -> Result<Self, JsValue> {
|
||||
let FitsData { hdu } = FitsData::from_reader(bytes_reader)
|
||||
.map_err(|_| {
|
||||
JsValue::from_str(&"Parsing fits error")
|
||||
})?;
|
||||
.map_err(|_| JsValue::from_str(&"Parsing fits error"))?;
|
||||
|
||||
let header = hdu.get_header();
|
||||
let xtension = header.get_xtension();
|
||||
let width = xtension.get_naxisn(1)
|
||||
let width = xtension
|
||||
.get_naxisn(1)
|
||||
.ok_or_else(|| JsValue::from_str("NAXIS1 not found in the fits"))?;
|
||||
|
||||
let height = xtension.get_naxisn(2)
|
||||
let height = xtension
|
||||
.get_naxisn(2)
|
||||
.ok_or_else(|| JsValue::from_str("NAXIS2 not found in the fits"))?;
|
||||
|
||||
|
||||
let data = hdu.get_data();
|
||||
let data = match *data {
|
||||
InMemData::U8(slice) => {
|
||||
Data::U8(Cow::Borrowed(slice))
|
||||
},
|
||||
InMemData::I16(slice) => {
|
||||
Data::I16(Cow::Borrowed(slice))
|
||||
},
|
||||
InMemData::I32(slice) => {
|
||||
Data::I32(Cow::Borrowed(slice))
|
||||
},
|
||||
InMemData::U8(slice) => Data::U8(Cow::Borrowed(slice)),
|
||||
InMemData::I16(slice) => Data::I16(Cow::Borrowed(slice)),
|
||||
InMemData::I32(slice) => Data::I32(Cow::Borrowed(slice)),
|
||||
InMemData::I64(slice) => {
|
||||
let data = slice.iter().map(|v| *v as i32).collect();
|
||||
Data::I32(Cow::Owned(data))
|
||||
},
|
||||
InMemData::F32(slice) => {
|
||||
Data::F32(Cow::Borrowed(slice))
|
||||
},
|
||||
}
|
||||
InMemData::F32(slice) => Data::F32(Cow::Borrowed(slice)),
|
||||
InMemData::F64(slice) => {
|
||||
let data = slice.iter().map(|v| *v as f32).collect();
|
||||
Data::F32(Cow::Owned(data))
|
||||
@@ -66,8 +55,8 @@ impl<'a> Fits<'a> {
|
||||
// Tile size
|
||||
size: Vector2::new(*width as i32, *height as i32),
|
||||
|
||||
// Allocation info of the layout
|
||||
data
|
||||
// Allocation info of the layout
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,14 +110,14 @@ impl<'a> Fits<'a> {
|
||||
// Tile size
|
||||
size: Vector2::new(*width as i32, *height as i32),
|
||||
|
||||
// Allocation info of the layout
|
||||
// Allocation info of the layout
|
||||
data
|
||||
})
|
||||
}
|
||||
}*/
|
||||
|
||||
use crate::Texture2DArray;
|
||||
use crate::image::Image;
|
||||
use crate::Texture2DArray;
|
||||
impl Image for Fits<'_> {
|
||||
fn tex_sub_image_3d(
|
||||
&self,
|
||||
@@ -138,7 +127,7 @@ impl Image for Fits<'_> {
|
||||
offset: &Vector3<i32>,
|
||||
) -> Result<(), JsValue> {
|
||||
match &self.data {
|
||||
Data::U8(data) => {
|
||||
Data::U8(data) => {
|
||||
let view = unsafe { R8UI::view(&data) };
|
||||
textures[offset.z as usize]
|
||||
.bind()
|
||||
@@ -150,7 +139,7 @@ impl Image for Fits<'_> {
|
||||
Some(view.as_ref()),
|
||||
);
|
||||
}
|
||||
Data::I16(data) => {
|
||||
Data::I16(data) => {
|
||||
let view = unsafe { R16I::view(&data) };
|
||||
textures[offset.z as usize]
|
||||
.bind()
|
||||
@@ -162,7 +151,7 @@ impl Image for Fits<'_> {
|
||||
Some(view.as_ref()),
|
||||
);
|
||||
}
|
||||
Data::I32(data) => {
|
||||
Data::I32(data) => {
|
||||
let view = unsafe { R32I::view(&data) };
|
||||
textures[offset.z as usize]
|
||||
.bind()
|
||||
@@ -174,7 +163,7 @@ impl Image for Fits<'_> {
|
||||
Some(view.as_ref()),
|
||||
);
|
||||
}
|
||||
Data::F32(data) => {
|
||||
Data::F32(data) => {
|
||||
let view = unsafe { R32F::view(&data) };
|
||||
textures[offset.z as usize]
|
||||
.bind()
|
||||
@@ -192,8 +181,8 @@ impl Image for Fits<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
use wasm_bindgen::JsValue;
|
||||
use crate::image::format::ImageFormat;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub trait FitsImageFormat: ImageFormat {
|
||||
const BITPIX: i8;
|
||||
@@ -205,7 +194,7 @@ impl FitsImageFormat for R32F {
|
||||
}
|
||||
|
||||
#[cfg(feature = "webgl2")]
|
||||
use crate::image::{R16I, R32I, R8UI, R64F};
|
||||
use crate::image::{R16I, R32I, R64F, R8UI};
|
||||
#[cfg(feature = "webgl2")]
|
||||
impl FitsImageFormat for R64F {
|
||||
const BITPIX: i8 = -64;
|
||||
|
||||
@@ -606,11 +606,11 @@ impl App {
|
||||
for rsc in rscs_received {
|
||||
match rsc {
|
||||
Resource::Tile(tile) => {
|
||||
if !has_camera_moved {
|
||||
if !_has_camera_zoomed {
|
||||
if let Some(survey) =
|
||||
self.layers.get_mut_hips_from_cdid(&tile.get_hips_cdid())
|
||||
{
|
||||
let cfg = survey.get_config();
|
||||
let cfg = survey.get_config_mut();
|
||||
|
||||
if cfg.get_format() == tile.format {
|
||||
let delta_depth = cfg.delta_depth();
|
||||
@@ -649,6 +649,63 @@ impl App {
|
||||
} else {
|
||||
Some(image)
|
||||
};
|
||||
use al_core::image::ImageType;
|
||||
use fitsrs::fits::Fits;
|
||||
use std::{io::Cursor, rc::Rc};
|
||||
if let Some(image) = image.as_ref() {
|
||||
match &*image.lock().unwrap_abort() {
|
||||
Some(ImageType::FitsImage {
|
||||
raw_bytes: raw_bytes_buf,
|
||||
}) => {
|
||||
// check if the metadata has not been set
|
||||
if !cfg.fits_metadata {
|
||||
let num_bytes =
|
||||
raw_bytes_buf.length() as usize;
|
||||
let mut raw_bytes = vec![0; num_bytes];
|
||||
raw_bytes_buf.copy_to(&mut raw_bytes[..]);
|
||||
|
||||
let mut bytes_reader =
|
||||
Cursor::new(raw_bytes.as_slice());
|
||||
let Fits { hdu } =
|
||||
Fits::from_reader(&mut bytes_reader)
|
||||
.map_err(|_| {
|
||||
JsValue::from_str(
|
||||
"Parsing fits error",
|
||||
)
|
||||
})?;
|
||||
|
||||
let header = hdu.get_header();
|
||||
let bscale = if let Some(
|
||||
fitsrs::card::Value::Float(bscale),
|
||||
) = header.get(b"BSCALE ")
|
||||
{
|
||||
*bscale as f32
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let bzero = if let Some(
|
||||
fitsrs::card::Value::Float(bzero),
|
||||
) = header.get(b"BZERO ")
|
||||
{
|
||||
*bzero as f32
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let blank = if let Some(
|
||||
fitsrs::card::Value::Float(blank),
|
||||
) = header.get(b"BLANK ")
|
||||
{
|
||||
*blank as f32
|
||||
} else {
|
||||
std::f32::NAN
|
||||
};
|
||||
|
||||
cfg.set_fits_metadata(bscale, bzero, blank);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
survey.add_tile(&cell, image, time_req)?;
|
||||
self.request_redraw = true;
|
||||
@@ -1501,6 +1558,10 @@ impl App {
|
||||
self.request_redraw = true;
|
||||
}
|
||||
|
||||
pub(crate) fn set_inertia(&mut self, inertia: bool) {
|
||||
*self.disable_inertia.borrow_mut() = !inertia;
|
||||
}
|
||||
|
||||
/*pub(crate) fn project_line(&self, lon1: f64, lat1: f64, lon2: f64, lat2: f64) -> Vec<Vector2<f64>> {
|
||||
let v1: Vector3<f64> = LonLatT::new(ArcDeg(lon1).into(), ArcDeg(lat1).into()).vector();
|
||||
let v2: Vector3<f64> = LonLatT::new(ArcDeg(lon2).into(), ArcDeg(lat2).into()).vector();
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::healpix::cell::HEALPixCell;
|
||||
use crate::healpix::coverage::HEALPixCoverage;
|
||||
use crate::math::angle::ToAngle;
|
||||
use crate::math::{projection::coo_space::XYZWModel, projection::domain::sdf::ProjDef};
|
||||
use al_core::log::console_log;
|
||||
use al_core::{info, inforec, log};
|
||||
|
||||
use cgmath::{Matrix4, Vector2};
|
||||
@@ -380,6 +381,8 @@ impl CameraViewPort {
|
||||
}
|
||||
};
|
||||
|
||||
//console_log(&format!("clip factor {:?}", self.aperture));
|
||||
|
||||
// Project this vertex into the screen
|
||||
self.moved = true;
|
||||
self.zoomed = true;
|
||||
|
||||
@@ -73,6 +73,7 @@ extern "C" {
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
use al_core::log::console_log;
|
||||
use math::projection::*;
|
||||
use renderable::coverage::moc::MOC;
|
||||
//use votable::votable::VOTableWrapper;
|
||||
@@ -484,6 +485,13 @@ impl WebClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = setInertia)]
|
||||
pub fn set_inertia(&mut self, inertia: bool) -> Result<(), JsValue> {
|
||||
self.app.set_inertia(inertia);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the absolute orientation of the view
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -384,29 +384,20 @@ impl ImageSurveyTextures {
|
||||
&mut self.base_textures[idx as usize]
|
||||
};*/
|
||||
|
||||
if let Some(image) = image {
|
||||
send_to_gpu(cell, texture, image, &self.texture_2d_array, &self.config)?;
|
||||
// Once the texture has been received in the GPU
|
||||
texture.append(
|
||||
cell, // The tile cell
|
||||
&self.config,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
send_to_gpu(
|
||||
cell,
|
||||
texture,
|
||||
self.config.get_default_image(),
|
||||
&self.texture_2d_array,
|
||||
&self.config,
|
||||
)?;
|
||||
// Once the texture has been received in the GPU
|
||||
texture.append(
|
||||
cell, // The tile cell
|
||||
&self.config,
|
||||
true,
|
||||
);
|
||||
};
|
||||
let missing = image.is_none();
|
||||
send_to_gpu(
|
||||
cell,
|
||||
texture,
|
||||
image,
|
||||
&self.texture_2d_array,
|
||||
&mut self.config,
|
||||
)?;
|
||||
|
||||
texture.append(
|
||||
cell, // The tile cell
|
||||
&self.config,
|
||||
missing,
|
||||
);
|
||||
|
||||
self.available_tiles_during_frame = true;
|
||||
//self.ready = true;
|
||||
@@ -638,9 +629,9 @@ impl ImageSurveyTextures {
|
||||
fn send_to_gpu<I: Image>(
|
||||
cell: &HEALPixCell,
|
||||
texture: &Texture,
|
||||
image: I,
|
||||
image: Option<I>,
|
||||
texture_array: &Texture2DArray,
|
||||
cfg: &HiPSConfig,
|
||||
cfg: &mut HiPSConfig,
|
||||
) -> Result<(), JsValue> {
|
||||
// Index of the texture in the total set of textures
|
||||
let texture_idx = texture.idx();
|
||||
@@ -672,7 +663,12 @@ fn send_to_gpu<I: Image>(
|
||||
idx_slice,
|
||||
);
|
||||
|
||||
image.tex_sub_image_3d(&texture_array, &offset)
|
||||
if let Some(image) = image {
|
||||
image.tex_sub_image_3d(&texture_array, &offset)
|
||||
} else {
|
||||
cfg.get_default_image()
|
||||
.tex_sub_image_3d(&texture_array, &offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl SendUniforms for ImageSurveyTextures {
|
||||
|
||||
@@ -156,6 +156,7 @@ pub struct HiPSConfig {
|
||||
// TODO: store this values in the ImageSurvey
|
||||
// These are proper to the survey (FITS one) and not
|
||||
// to a specific survey color
|
||||
pub fits_metadata: bool,
|
||||
pub scale: f32,
|
||||
pub offset: f32,
|
||||
pub blank: f32,
|
||||
@@ -180,7 +181,7 @@ use wasm_bindgen::JsValue;
|
||||
|
||||
const NUM_TEXTURES_BY_SIDE_SLICE: i32 = 8;
|
||||
const NUM_TEXTURES_BY_SLICE: i32 = NUM_TEXTURES_BY_SIDE_SLICE * NUM_TEXTURES_BY_SIDE_SLICE;
|
||||
const NUM_SLICES: i32 = 2;
|
||||
const NUM_SLICES: i32 = 1;
|
||||
|
||||
impl HiPSConfig {
|
||||
/// Define a HiPS configuration
|
||||
@@ -328,6 +329,7 @@ impl HiPSConfig {
|
||||
|
||||
is_allsky,
|
||||
|
||||
fits_metadata: false,
|
||||
scale: 1.0,
|
||||
offset: 0.0,
|
||||
blank: -1.0, // by default, set it to -1
|
||||
@@ -449,6 +451,7 @@ impl HiPSConfig {
|
||||
self.scale = bscale;
|
||||
self.offset = bzero;
|
||||
self.blank = blank;
|
||||
self.fits_metadata = true;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
position: relative;
|
||||
border: 0px solid #ddd;
|
||||
/* SVG inside divs add a 4px height: https://stackoverflow.com/questions/75751593/why-there-is-additional-4px-height-for-div-when-there-is-svg-inside-it */
|
||||
|
||||
/* disable x swipe on chrome, firefox */
|
||||
/* see. https://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward */
|
||||
overscroll-behavior-x: none;
|
||||
@@ -17,12 +16,6 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/*.aladin-gridCanvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}*/
|
||||
|
||||
.aladin-catalogCanvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -451,6 +444,7 @@ canvas {
|
||||
background-image:none;
|
||||
text-indent: 0rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
.aladin-input-text.search.aladin-unknownObject {
|
||||
|
||||
@@ -49,6 +49,7 @@ import { ImageFITS } from "./ImageFITS.js";
|
||||
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
|
||||
import { SAMPConnector } from "./vo/samp.js";
|
||||
import { Reticle } from "./Reticle.js";
|
||||
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
|
||||
|
||||
// GUI
|
||||
import { AladinLogo } from "./gui/AladinLogo.js";
|
||||
@@ -271,7 +272,7 @@ export let Aladin = (function () {
|
||||
|
||||
// Grid
|
||||
let gridOptions = options.gridOptions;
|
||||
console.log(options.gridOptions)
|
||||
|
||||
// color and opacity can be defined by two variables. The item in gridOptions
|
||||
// should take precedence.
|
||||
gridOptions["color"] = options.gridOptions.color || options.gridColor;
|
||||
@@ -279,7 +280,7 @@ export let Aladin = (function () {
|
||||
if (options && options.showCooGrid) {
|
||||
gridOptions.enabled = true;
|
||||
}
|
||||
console.log(gridOptions)
|
||||
|
||||
this.setCooGrid(gridOptions);
|
||||
|
||||
this.gotoObject(options.target, undefined);
|
||||
@@ -368,6 +369,10 @@ export let Aladin = (function () {
|
||||
this.samp = new SAMPConnector(this);
|
||||
}
|
||||
|
||||
if (options.inertia !== undefined) {
|
||||
this.wasm.setInertia(options.inertia)
|
||||
}
|
||||
|
||||
this._setupUI(options);
|
||||
};
|
||||
|
||||
@@ -493,6 +498,7 @@ export let Aladin = (function () {
|
||||
target: "0 +0",
|
||||
cooFrame: "J2000",
|
||||
fov: 60,
|
||||
inertia: true,
|
||||
backgroundColor: "rgb(60, 60, 60)",
|
||||
// Zoom toolbar
|
||||
showZoomControl: false,
|
||||
@@ -935,9 +941,9 @@ export let Aladin = (function () {
|
||||
|
||||
var idTimeoutAnim;
|
||||
var doAnimation = function (aladin) {
|
||||
if (idTimeoutAnim) {
|
||||
/*if (idTimeoutAnim) {
|
||||
clearTimeout(idTimeoutAnim)
|
||||
}
|
||||
}*/
|
||||
|
||||
var params = aladin.animationParams;
|
||||
if (params == null || !params['running']) {
|
||||
@@ -964,8 +970,11 @@ export let Aladin = (function () {
|
||||
//var curDec = params['decStart'] + (params['decEnd'] - params['decStart']) * (now-params['start']) / (params['end'] - params['start']);
|
||||
|
||||
aladin.gotoRaDec(curRa, curDec);
|
||||
|
||||
idTimeoutAnim = setTimeout(function () { doAnimation(aladin); }, 10);
|
||||
|
||||
//idTimeoutAnim = setTimeout(function () { doAnimation(aladin); }, 10);
|
||||
requestAnimFrame(() => {
|
||||
doAnimation(aladin)
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -42,7 +42,6 @@ import { Ellipse } from "./Ellipse.js";
|
||||
import { Circle } from "./Circle.js";
|
||||
import { Footprint } from "./Footprint.js";
|
||||
|
||||
|
||||
/**
|
||||
* Represents a catalog with configurable options for display and interaction.
|
||||
*
|
||||
@@ -497,10 +496,6 @@ export let Catalog = (function() {
|
||||
this.setFields(fields);
|
||||
}
|
||||
|
||||
//let footprints = this.parseFootprintsFromSources(sources);
|
||||
//sources = sources.filter((s) => s.hasFootprint !== true);
|
||||
//this.addFootprints(footprints);
|
||||
|
||||
this.sources = this.sources.concat(sources);
|
||||
for (var k=0, len=sources.length; k<len; k++) {
|
||||
sources[k].setCatalog(this);
|
||||
@@ -713,19 +708,20 @@ export let Catalog = (function() {
|
||||
};
|
||||
|
||||
Catalog.prototype.draw = function(ctx, width, height) {
|
||||
if (! this.isShowing) {
|
||||
if (!this.isShowing) {
|
||||
return;
|
||||
}
|
||||
// tracé simple
|
||||
ctx.strokeStyle = this.color;
|
||||
|
||||
// Draw the footprints
|
||||
// Draw the footprints first
|
||||
this.drawFootprints(ctx);
|
||||
|
||||
if (this._shapeIsFunction) {
|
||||
ctx.save();
|
||||
}
|
||||
const sourcesInView = this.drawSources(ctx, width, height);
|
||||
|
||||
const drawnSources = this.drawSources(ctx, width, height);
|
||||
|
||||
if (this._shapeIsFunction) {
|
||||
ctx.restore();
|
||||
@@ -735,18 +731,19 @@ export let Catalog = (function() {
|
||||
if (this.displayLabel) {
|
||||
ctx.fillStyle = this.labelColor;
|
||||
ctx.font = this.labelFont;
|
||||
sourcesInView.forEach((s) => {
|
||||
drawnSources.forEach((s) => {
|
||||
this.drawSourceLabel(s, ctx);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Catalog.prototype.drawSources = function(ctx, width, height) {
|
||||
let inside = [];
|
||||
|
||||
if (!this.sources) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sourcesInsideView = [];
|
||||
|
||||
let xy = this.view.wasm.worldToScreenVec(this.ra, this.dec);
|
||||
|
||||
let self = this;
|
||||
@@ -757,12 +754,12 @@ export let Catalog = (function() {
|
||||
s.y = xy[2*idx + 1];
|
||||
|
||||
self.drawSource(s, ctx, width, height)
|
||||
sourcesInsideView.push(s);
|
||||
inside.push(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sourcesInsideView;
|
||||
return inside;
|
||||
};
|
||||
|
||||
Catalog.prototype.drawSource = function(s, ctx, width, height) {
|
||||
@@ -770,7 +767,7 @@ export let Catalog = (function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.hasFootprint) {
|
||||
if (s.hasFootprint && !s.tooSmallFootprint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -821,8 +818,14 @@ export let Catalog = (function() {
|
||||
this.recomputeFootprints = false;
|
||||
}
|
||||
|
||||
var f;
|
||||
for(let k = 0; k < this.footprints.length; k++) {
|
||||
this.footprints[k].draw(ctx, this.view)
|
||||
f = this.footprints[k];
|
||||
|
||||
f.draw(ctx, this.view)
|
||||
f.source.tooSmallFootprint = f.isTooSmall()
|
||||
// propagate the info that the footprint is too small
|
||||
//f.source.tooSmallFootprint = f.isTooSmall
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ export let Circle = (function() {
|
||||
// TODO
|
||||
Circle.prototype.draw = function(ctx, view, noStroke) {
|
||||
if (! this.isShowing) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
@@ -207,7 +207,7 @@ export let Circle = (function() {
|
||||
if (!centerXyview) {
|
||||
// the center goes out of the projection
|
||||
// we do not draw it
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.center = {
|
||||
x: centerXyview[0],
|
||||
@@ -237,7 +237,7 @@ export let Circle = (function() {
|
||||
});
|
||||
|
||||
if (hidden) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// Then we can draw
|
||||
|
||||
@@ -271,6 +271,8 @@ export let Circle = (function() {
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Circle.prototype.isInStroke = function(ctx, view, x, y) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
import { Utils } from "./Utils";
|
||||
import { Overlay } from "./Overlay.js";
|
||||
import { requestAnimFrame } from "./libs/RequestAnimationFrame";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ShapeOptions
|
||||
@@ -231,22 +232,25 @@ export let Ellipse = (function() {
|
||||
}
|
||||
|
||||
// TODO
|
||||
Ellipse.prototype.draw = function(ctx, view, noStroke) {
|
||||
Ellipse.prototype.draw = function(ctx, view, noStroke, noSmallCheck) {
|
||||
if (! this.isShowing) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let px_per_deg = view.width / view.fov;
|
||||
|
||||
/*if (this.a * 2 * px_per_deg < this.lineWidth || this.b * 2 * px_per_deg < this.lineWidth) {
|
||||
return;
|
||||
}*/
|
||||
const px_per_deg = view.width / view.fov;
|
||||
noSmallCheck = noSmallCheck===true || false;
|
||||
if (!noSmallCheck) {
|
||||
this.isTooSmall = this.b * 2 * px_per_deg < this.lineWidth;
|
||||
if (this.isTooSmall) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var originScreen = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
|
||||
if (!originScreen) {
|
||||
// the center goes out of the projection
|
||||
// we do not draw it
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Find the spherical tangent vector going to the north
|
||||
@@ -256,7 +260,7 @@ export let Ellipse = (function() {
|
||||
let toNorthScreen = view.aladin.world2pix(toNorth[0], toNorth[1]);
|
||||
|
||||
if(!toNorthScreen) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. normalize this vector
|
||||
@@ -338,10 +342,15 @@ export let Ellipse = (function() {
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Ellipse.prototype.isInStroke = function(ctx, view, x, y) {
|
||||
this.draw(ctx, view, true);
|
||||
if (!this.draw(ctx, view, true, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ctx.isPointInStroke(x, y);
|
||||
};
|
||||
|
||||
|
||||
@@ -56,11 +56,6 @@ export class CircleSelect extends FSM {
|
||||
let draw = () => {
|
||||
let ctx = view.catalogCtx;
|
||||
|
||||
/*if (!view.catalogCanvasCleared) {
|
||||
ctx.clearRect(0, 0, view.width, view.height);
|
||||
view.catalogCanvasCleared = true;
|
||||
}*/
|
||||
|
||||
// draw the selection
|
||||
ctx.fillStyle = options.color + '7f';
|
||||
ctx.strokeStyle = options.color;
|
||||
|
||||
@@ -119,11 +119,6 @@ export class PolySelect extends FSM {
|
||||
let draw = () => {
|
||||
let ctx = view.catalogCtx;
|
||||
|
||||
/*if (!view.catalogCanvasCleared) {
|
||||
ctx.clearRect(0, 0, view.width, view.height);
|
||||
view.catalogCanvasCleared = true;
|
||||
}*/
|
||||
|
||||
// draw the selection
|
||||
ctx.save();
|
||||
ctx.fillStyle = options.color + '7f';
|
||||
|
||||
@@ -57,11 +57,6 @@ export class RectSelect extends FSM {
|
||||
let draw = () => {
|
||||
let ctx = view.catalogCtx;
|
||||
|
||||
/*if (!view.catalogCanvasCleared) {
|
||||
ctx.clearRect(0, 0, view.width, view.height);
|
||||
view.catalogCanvasCleared = true;
|
||||
}*/
|
||||
|
||||
// draw the selection
|
||||
ctx.fillStyle = options.color + '7f';
|
||||
ctx.strokeStyle = options.color;
|
||||
|
||||
@@ -170,7 +170,7 @@ export let Footprint= (function() {
|
||||
}
|
||||
|
||||
Footprint.prototype.draw = function(ctx, view, noStroke) {
|
||||
this.shapes.forEach((shape) => shape.draw(ctx, view, noStroke))
|
||||
return this.shapes.some((shape) => {return shape.draw(ctx, view, noStroke)})
|
||||
};
|
||||
|
||||
Footprint.prototype.actionClicked = function() {
|
||||
@@ -192,6 +192,10 @@ export let Footprint= (function() {
|
||||
return this.shapes.some((shape) => shape.isInStroke(ctx, view, x, y));
|
||||
};
|
||||
|
||||
Footprint.prototype.isTooSmall = function(view) {
|
||||
return this.shapes.every((shape) => shape.isTooSmall);
|
||||
};
|
||||
|
||||
Footprint.prototype.getCatalog = function() {
|
||||
return this.source && this.source.catalog;
|
||||
};
|
||||
|
||||
@@ -355,6 +355,7 @@ export let ImageSurvey = (function () {
|
||||
minCut = self.colorCfg.minCut || 0.0;
|
||||
maxCut = self.colorCfg.maxCut || 1.0;
|
||||
}
|
||||
|
||||
self.colorCfg.setCuts(minCut, maxCut);
|
||||
|
||||
// Coo frame
|
||||
@@ -469,6 +470,7 @@ export let ImageSurvey = (function () {
|
||||
// Switch from png/webp/jpeg to fits
|
||||
if ((self.imgFormat === 'png' || self.imgFormat === "webp" || self.imgFormat === "jpeg") && imgFormat === 'fits') {
|
||||
if (self.minCut && self.maxCut) {
|
||||
// reset cuts to those given from the properties
|
||||
self.setCuts(self.minCut, self.maxCut)
|
||||
}
|
||||
// Switch from fits to png/webp/jpeg
|
||||
@@ -891,7 +893,7 @@ export let ImageSurvey = (function () {
|
||||
imgFormat: 'jpeg',
|
||||
cooFrame: 'equatorial'
|
||||
},
|
||||
/*SDSS9_g: {
|
||||
SDSS9_g: {
|
||||
creatorDid: "ivo://CDS/P/SDSS9/g",
|
||||
id: "P/SDSS9/g",
|
||||
name: "SDSS9 band-g",
|
||||
@@ -905,7 +907,7 @@ export let ImageSurvey = (function () {
|
||||
maxCut: 1.8,
|
||||
stretch: 'linear',
|
||||
colormap: "redtemperature",
|
||||
}*/
|
||||
}
|
||||
/*
|
||||
{
|
||||
id: "P/Finkbeiner",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
import { Polyline } from "./Polyline.js";
|
||||
import { Utils } from './Utils';
|
||||
import { Overlay } from "./Overlay.js";
|
||||
import { Ellipse } from "./Ellipse.js";
|
||||
|
||||
/**
|
||||
* Represents an line shape
|
||||
@@ -99,33 +100,36 @@ export let Line = (function() {
|
||||
setSelectionColor: Polyline.prototype.setSelectionColor,
|
||||
setHoverColor: Polyline.prototype.setHoverColor,
|
||||
|
||||
draw: function(ctx, view, noStroke) {
|
||||
draw: function(ctx, view, noStroke, noSmallCheck) {
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
noSmallCheck = noSmallCheck===true || false;
|
||||
// project
|
||||
const v1 = view.aladin.world2pix(this.ra1, this.dec1, this.frame);
|
||||
if (!v1)
|
||||
return;
|
||||
return false;
|
||||
const v2 = view.aladin.world2pix(this.ra2, this.dec2, this.frame);
|
||||
if (!v2)
|
||||
return;
|
||||
return false;
|
||||
|
||||
const xmin = Math.min(v1.x, v2.x);
|
||||
const xmax = Math.max(v1.x, v2.x);
|
||||
const ymin = Math.min(v1.y, v2.y);
|
||||
const ymax = Math.max(v1.y, v2.y);
|
||||
const xmin = Math.min(v1[0], v2[0]);
|
||||
const xmax = Math.max(v1[0], v2[0]);
|
||||
const ymin = Math.min(v1[1], v2[1]);
|
||||
const ymax = Math.max(v1[1], v2[1]);
|
||||
|
||||
// out of bbox
|
||||
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let baseColor = this.color || (this.overlay && this.overlay.color) || '#ff0000';
|
||||
let lineWidth = this.lineWidth || this.overlay.lineWidth || 3;
|
||||
|
||||
// too small
|
||||
if ((xmax - xmin) < lineWidth || (ymax - ymin) < lineWidth) {
|
||||
return;
|
||||
if(!noSmallCheck) {
|
||||
this.isTooSmall = (xmax - xmin) < 1 && (ymax - ymin) < 1;
|
||||
if (this.isTooSmall) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isSelected) {
|
||||
@@ -171,12 +175,12 @@ export let Line = (function() {
|
||||
if (!noStroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isInStroke: function(ctx, view, x, y) {
|
||||
this.draw(ctx, view, true);
|
||||
return ctx.isPointInStroke(x, y);
|
||||
},
|
||||
isInStroke: Ellipse.prototype.isInStroke,
|
||||
|
||||
/*Line.prototype.intersectsBBox = function(x, y, w, h) {
|
||||
// todo
|
||||
};*/
|
||||
|
||||
@@ -243,11 +243,11 @@ export let Polyline = (function() {
|
||||
|
||||
Polyline.prototype.draw = function(ctx, view, noStroke) {
|
||||
if (! this.isShowing) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! this.radecArray || this.radecArray.length<2) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
noStroke = noStroke===true || false;
|
||||
@@ -289,7 +289,7 @@ export let Polyline = (function() {
|
||||
for (var k=0; k<len; k++) {
|
||||
var xyview = view.aladin.world2pix(this.radecArray[k][0], this.radecArray[k][1]);
|
||||
if (!xyview) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
xyView.push({x: xyview[0], y: xyview[1]});
|
||||
@@ -302,11 +302,11 @@ export let Polyline = (function() {
|
||||
|
||||
// 2. do not draw the polygon if it lies in less than linewidth pixels
|
||||
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((xmax - xmin) < this.lineWidth || (ymax - ymin) < this.lineWidth) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let drawLine;
|
||||
@@ -451,6 +451,8 @@ export let Polyline = (function() {
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Polyline.prototype.isInStroke = function(ctx, view, x, y) {
|
||||
|
||||
@@ -90,7 +90,6 @@ export let Popup = (function() {
|
||||
}
|
||||
source.popup = this;
|
||||
this.source = source;
|
||||
|
||||
this.setPosition(source.x, source.y);
|
||||
};
|
||||
|
||||
|
||||
@@ -99,13 +99,14 @@ export class Selector {
|
||||
if (view.catalogs) {
|
||||
for (var k = 0; k < view.catalogs.length; k++) {
|
||||
cat = view.catalogs[k];
|
||||
|
||||
if (!cat.isShowing) {
|
||||
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;
|
||||
}
|
||||
if (selection.contains(s)) {
|
||||
|
||||
@@ -62,7 +62,7 @@ export let SimbadPointer = (function() {
|
||||
}
|
||||
content += '<br><a target="_blank" href="http://cdsportal.u-strasbg.fr/?target=' + encodeURIComponent(objName) + '">Query in CDS portal</a>';
|
||||
content += '</div>';
|
||||
|
||||
|
||||
aladinInstance.showPopup(objCoo.lon, objCoo.lat, title, content);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -125,6 +125,39 @@ Utils.isInt = function (n: string | number) {
|
||||
return Utils.isNumber(n) && Math.floor(n as number) === n
|
||||
}
|
||||
|
||||
// Newton-Raphson method to find the approximate inverse of f(x)
|
||||
Utils.inverseNewtonRaphson = function(y: number, f: Function, fPrime: Function, tolerance=1e-6, maxIterations=100) {
|
||||
let x_guess = 0.5; // Initial guess
|
||||
let iteration = 0;
|
||||
|
||||
while (iteration < maxIterations) {
|
||||
let f_x = f(x_guess);
|
||||
let error = Math.abs(f_x - y);
|
||||
|
||||
if (error < tolerance) {
|
||||
return x_guess; // Found approximate inverse
|
||||
}
|
||||
|
||||
let derivative = fPrime(x_guess);
|
||||
x_guess = x_guess - (f_x - y) / derivative; // Newton-Raphson update
|
||||
iteration++;
|
||||
}
|
||||
|
||||
return null; // No convergence within maxIterations
|
||||
}
|
||||
|
||||
Utils.binarySearch = function(array, value) {
|
||||
var low = 0,
|
||||
high = array.length;
|
||||
|
||||
while (low < high) {
|
||||
var mid = (low + high) >>> 1;
|
||||
if (array[mid] > value) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
/* a debounce function, used to prevent multiple calls to the same function if less than delay milliseconds have passed */
|
||||
Utils.debounce = function (fn, delay) {
|
||||
var timer = null
|
||||
|
||||
225
src/js/View.js
225
src/js/View.js
@@ -43,7 +43,7 @@ import { CooFrameEnum } from "./CooFrameEnum.js";
|
||||
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
|
||||
import { WebGLCtx } from "./WebGL.js";
|
||||
import { ALEvent } from "./events/ALEvent.js";
|
||||
import { ColorCfg } from "./ColorCfg.js";
|
||||
import { Zoom } from './Zoom.js'
|
||||
import { Footprint } from "./Footprint.js";
|
||||
import { Selector } from "./Selector.js";
|
||||
import { ObsCore } from "./vo/ObsCore.js";
|
||||
@@ -133,14 +133,11 @@ export let View = (function () {
|
||||
lon = lat = 0;
|
||||
|
||||
// FoV init settings
|
||||
const si = 500000.0;
|
||||
const alpha = 40.0;
|
||||
let initialFov = this.options.fov || 180.0;
|
||||
this.pinchZoomParameters = {
|
||||
isPinching: false, // true if a pinch zoom is ongoing
|
||||
initialFov: undefined,
|
||||
initialDistance: undefined,
|
||||
initialAccDelta: Math.pow(si / initialFov, 1.0 / alpha)
|
||||
};
|
||||
|
||||
// Projection definition
|
||||
@@ -148,7 +145,8 @@ export let View = (function () {
|
||||
this.setProjection(projName)
|
||||
|
||||
// Then set the zoom properly once the projection is defined
|
||||
this.setZoom(initialFov);
|
||||
this.wasm.setFieldOfView(initialFov);
|
||||
this.updateZoomState();
|
||||
|
||||
// Target position settings
|
||||
this.viewCenter = { lon, lat }; // position of center of view
|
||||
@@ -216,6 +214,7 @@ export let View = (function () {
|
||||
initialFingerAngle: undefined,
|
||||
rotationInitiated: false
|
||||
}
|
||||
this.zoom = new Zoom(this);
|
||||
|
||||
this.fadingLatestUpdate = null;
|
||||
this.dateRequestRedraw = null;
|
||||
@@ -665,8 +664,6 @@ export let View = (function () {
|
||||
view.dragging = false;
|
||||
|
||||
view.pinchZoomParameters.isPinching = true;
|
||||
//var fov = view.aladin.getFov();
|
||||
//view.pinchZoomParameters.initialFov = Math.max(fov[0], fov[1]);
|
||||
var fov = view.wasm.getFieldOfView();
|
||||
view.pinchZoomParameters.initialFov = fov;
|
||||
view.pinchZoomParameters.initialDistance = Math.sqrt(Math.pow(e.targetTouches[0].clientX - e.targetTouches[1].clientX, 2) + Math.pow(e.targetTouches[0].clientY - e.targetTouches[1].clientY, 2));
|
||||
@@ -1062,13 +1059,21 @@ export let View = (function () {
|
||||
var eventCount = 0;
|
||||
var eventCountStart;
|
||||
var isTouchPad;
|
||||
var scale = 0.0;
|
||||
let id;
|
||||
|
||||
Utils.on(view.catalogCanvas, 'wheel', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const xymouse = Utils.relMouseCoords(e);
|
||||
view.wheelTriggered = true;
|
||||
|
||||
clearTimeout(id);
|
||||
id = setTimeout(() => {
|
||||
view.wheelTriggered = false;
|
||||
}, 100);
|
||||
|
||||
const xymouse = Utils.relMouseCoords(e);
|
||||
view.xy = xymouse
|
||||
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
@@ -1083,21 +1088,25 @@ export let View = (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = e.deltaY || e.detail || (-e.wheelDelta);
|
||||
if (!view.debounceProgCatOnZoom) {
|
||||
var self = view;
|
||||
view.debounceProgCatOnZoom = Utils.debounce(function () {
|
||||
self.refreshProgressiveCats();
|
||||
self.drawAllOverlays();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Limit the minimum and maximum zoom levels
|
||||
//var delta = e.deltaY;
|
||||
// this seems to happen in context of Jupyter notebook --> we have to invert the direction of scroll
|
||||
// hope this won't trigger some side effects ...
|
||||
/*if (e.hasOwnProperty('originalEvent')) {
|
||||
delta = -e.deltaY;
|
||||
}*/
|
||||
view.debounceProgCatOnZoom();
|
||||
view.throttledZoomChanged();
|
||||
|
||||
// Zoom heuristic
|
||||
// First detect the device
|
||||
// See https://stackoverflow.com/questions/10744645/detect-touchpad-vs-mouse-in-javascript
|
||||
// for detecting the use of a touchpad
|
||||
var isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
|
||||
if (!isTouchPadDefined) {
|
||||
view.isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
|
||||
if (!view.isTouchPadDefined) {
|
||||
if (eventCount === 0) {
|
||||
view.delta = 0;
|
||||
eventCountStart = new Date().getTime();
|
||||
}
|
||||
|
||||
@@ -1109,50 +1118,75 @@ export let View = (function () {
|
||||
} else {
|
||||
isTouchPad = false;
|
||||
}
|
||||
isTouchPadDefined = true;
|
||||
view.isTouchPadDefined = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The value of the field of view is determined
|
||||
// inside the backend
|
||||
const triggerZoom = (amount) => {
|
||||
if (delta < 0.0) {
|
||||
view.increaseZoom(amount);
|
||||
} else {
|
||||
view.decreaseZoom(amount);
|
||||
}
|
||||
};
|
||||
|
||||
if (isTouchPadDefined) {
|
||||
let dt = performance.now() - view.then
|
||||
|
||||
let a0, a1;
|
||||
|
||||
// touchpad
|
||||
if (isTouchPad) {
|
||||
a1 = 0.002;
|
||||
a0 = 0.0002;
|
||||
} else {
|
||||
a1 = 0.01;
|
||||
a0 = 0.0004;
|
||||
}
|
||||
|
||||
const alpha = Math.pow(view.fov / view.projection.fov, 0.5);
|
||||
|
||||
const lerp = a0 * alpha + a1 * (1.0 - alpha);
|
||||
triggerZoom(lerp);
|
||||
// only ensure the touch pad test has been done before zooming
|
||||
if (!view.isTouchPadDefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!view.debounceProgCatOnZoom) {
|
||||
var self = view;
|
||||
view.debounceProgCatOnZoom = Utils.debounce(function () {
|
||||
self.refreshProgressiveCats();
|
||||
self.drawAllOverlays();
|
||||
}, 300);
|
||||
}
|
||||
// touch pad defined
|
||||
|
||||
view.debounceProgCatOnZoom();
|
||||
view.throttledZoomChanged();
|
||||
if (isTouchPad) {
|
||||
view.delta = e.deltaY || e.detail || (-e.wheelDelta);
|
||||
|
||||
if (!view.throttledTouchPadZoom) {
|
||||
let radec;
|
||||
view.throttledTouchPadZoom = Utils.throttle(() => {
|
||||
if (!view.zoom.isZooming && !view.wheelTriggered) {
|
||||
// start zooming detected
|
||||
radec = view.aladin.pix2world(view.xy.x, view.xy.y);
|
||||
}
|
||||
|
||||
let amount = view.delta > 0 ? -Zoom.MAX_IDX_DELTA_PER_TROTTLE : Zoom.MAX_IDX_DELTA_PER_TROTTLE;
|
||||
if (amount === 0)
|
||||
return;
|
||||
|
||||
// change the zoom level
|
||||
let newFov = Zoom.determineNextFov(view, amount);
|
||||
view.zoom.apply({
|
||||
stop: newFov,
|
||||
duration: 300
|
||||
});
|
||||
//view.setZoom(newFov)
|
||||
|
||||
/*if (amount > 0 && radec) {
|
||||
let sRaDec = view.aladin.getRaDec();
|
||||
|
||||
let moveTo = function() {
|
||||
const t = 1 - (view.x - view.x1) / (view.x2 - view.x1);
|
||||
|
||||
let ra = (0.5 + t*0.5) * sRaDec[0] + (0.5 - t*0.5) * radec[0]
|
||||
let dec = (0.5 + t*0.5) * sRaDec[1] + (0.5 - t*0.5) * radec[1]
|
||||
|
||||
view.aladin.gotoRaDec(ra, dec)
|
||||
|
||||
if (t >= 1e-2)
|
||||
requestAnimFrame(moveTo)
|
||||
}
|
||||
//requestAnimFrame(moveTo)
|
||||
}*/
|
||||
}, 30);
|
||||
}
|
||||
|
||||
view.throttledTouchPadZoom();
|
||||
} else {
|
||||
if (!view.throttledMouseScrollZoom) {
|
||||
view.throttledMouseScrollZoom = Utils.throttle(() => {
|
||||
let newFov = view.delta > 0 ? view.fov * 1.15 : view.fov / 1.15;
|
||||
// standard mouse wheel zooming
|
||||
|
||||
view.zoom.apply({
|
||||
stop: newFov,
|
||||
duration: 300
|
||||
});
|
||||
}, 30);
|
||||
}
|
||||
|
||||
view.throttledMouseScrollZoom()
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
@@ -1257,11 +1291,11 @@ export let View = (function () {
|
||||
this.catalogCanvasCleared = true;
|
||||
}
|
||||
|
||||
this.catalogForPopup.draw(ctx, this.cooFrame, this.width, this.height, this.largestDim);
|
||||
this.catalogForPopup.draw(ctx, this.width, this.height);
|
||||
|
||||
// draw popup overlay layer
|
||||
if (this.overlayForPopup.isShowing) {
|
||||
this.overlayForPopup.draw(ctx, this.cooFrame, this.width, this.height, this.largestDim);
|
||||
this.overlayForPopup.draw(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,6 +1347,11 @@ export let View = (function () {
|
||||
}
|
||||
|
||||
if (this.mode === View.SELECT) {
|
||||
if (!this.catalogCanvasCleared) {
|
||||
ctx.clearRect(0, 0, this.width, this.height);
|
||||
this.catalogCanvasCleared = true;
|
||||
}
|
||||
|
||||
this.selector.dispatch('draw')
|
||||
}
|
||||
};
|
||||
@@ -1425,7 +1464,6 @@ export let View = (function () {
|
||||
};
|
||||
|
||||
// Called for touchmove events
|
||||
// initialAccDelta must be consistent with fovDegrees here
|
||||
View.prototype.setZoom = function (fov) {
|
||||
// limit the fov in function of the projection
|
||||
fov = Math.min(fov, this.projection.fov);
|
||||
@@ -1443,43 +1481,21 @@ export let View = (function () {
|
||||
}
|
||||
|
||||
this.wasm.setFieldOfView(fov);
|
||||
|
||||
this.updateZoomState();
|
||||
};
|
||||
|
||||
View.prototype.increaseZoom = function (amount) {
|
||||
const si = 500000.0;
|
||||
const alpha = 40.0;
|
||||
|
||||
let initialAccDelta = this.pinchZoomParameters.initialAccDelta + amount;
|
||||
let new_fov = si / Math.pow(initialAccDelta, alpha);
|
||||
|
||||
if (new_fov < 0.00002777777) {
|
||||
new_fov = 0.00002777777;
|
||||
}
|
||||
|
||||
this.pinchZoomParameters.initialAccDelta = initialAccDelta;
|
||||
this.setZoom(new_fov);
|
||||
View.prototype.increaseZoom = function () {
|
||||
this.zoom.apply({
|
||||
stop: Zoom.determineNextFov(this, 6),
|
||||
duration: 300
|
||||
});
|
||||
}
|
||||
|
||||
View.prototype.decreaseZoom = function (amount) {
|
||||
const si = 500000.0;
|
||||
const alpha = 40.0;
|
||||
|
||||
let initialAccDelta = this.pinchZoomParameters.initialAccDelta - amount;
|
||||
|
||||
if (initialAccDelta <= 0.0) {
|
||||
initialAccDelta = 1e-3;
|
||||
}
|
||||
|
||||
let new_fov = si / Math.pow(initialAccDelta, alpha);
|
||||
|
||||
if (new_fov >= this.projection.fov) {
|
||||
new_fov = this.projection.fov;
|
||||
}
|
||||
|
||||
this.pinchZoomParameters.initialAccDelta = initialAccDelta;
|
||||
this.setZoom(new_fov);
|
||||
View.prototype.decreaseZoom = function () {
|
||||
this.zoom.apply({
|
||||
stop: Zoom.determineNextFov(this, -6),
|
||||
duration: 300
|
||||
});
|
||||
}
|
||||
|
||||
View.prototype.setRotation = function(rotation) {
|
||||
@@ -1507,11 +1523,6 @@ export let View = (function () {
|
||||
// Get the new zoom values from the backend
|
||||
let fov = this.wasm.getFieldOfView();
|
||||
|
||||
// Update the pinch zoom parameters consequently
|
||||
const si = 500000.0;
|
||||
const alpha = 40.0;
|
||||
this.pinchZoomParameters.initialAccDelta = Math.pow(si / fov, 1.0 / alpha);
|
||||
|
||||
// Save it
|
||||
this.fov = fov;
|
||||
this.computeNorder();
|
||||
@@ -1987,17 +1998,19 @@ export let View = (function () {
|
||||
let closest = null;
|
||||
|
||||
footprints.forEach((footprint) => {
|
||||
// Hidden footprints are not considered
|
||||
let lineWidth = footprint.getLineWidth();
|
||||
if (!footprint.source.tooSmallFootprint) {
|
||||
// Hidden footprints are not considered
|
||||
let lineWidth = footprint.getLineWidth();
|
||||
|
||||
footprint.setLineWidth(10.0);
|
||||
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
|
||||
closest = footprint;
|
||||
}
|
||||
footprint.setLineWidth(lineWidth);
|
||||
footprint.setLineWidth(10.0);
|
||||
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
|
||||
closest = footprint;
|
||||
}
|
||||
footprint.setLineWidth(lineWidth);
|
||||
|
||||
if (closest) {
|
||||
return closest;
|
||||
if (closest) {
|
||||
return closest;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
168
src/js/Zoom.js
Normal file
168
src/js/Zoom.js
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
* File Tile
|
||||
*
|
||||
* Author: Matthieu Baumann[CDS]
|
||||
*
|
||||
*****************************************************************************/
|
||||
import { Utils } from "./Utils";
|
||||
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
|
||||
|
||||
|
||||
export let Zoom = (function() {
|
||||
// constructor
|
||||
function Zoom(view) {
|
||||
this.view = view;
|
||||
};
|
||||
|
||||
Zoom.LEVELS = [
|
||||
360, 330, 300, 275, 250, 225, 200, 190,
|
||||
180, 170, 160, 150, 140, 130, 120, 110, 100,
|
||||
95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 18, 16, 14, 12, 10,
|
||||
9, 8, 7, 6, 5, 4, 3, 2, 1.75, 1.5, 1.25, 1,
|
||||
55/60, 50/60, 45/60, 40/60, 35/60, 30/60, 25/60, 20/60, 15/60, 10/60,
|
||||
9/60, 8/60, 7/60, 6/60, 5/60, 4/60, 3/60, 2/60, 1/60,
|
||||
50/3600, 40/3600, 30/3600, 20/3600, 10/3600,
|
||||
9/3600, 8/3600, 7/3600, 6/3600, 5/3600, 4/3600, 3/3600, 2/3600, 1/3600,
|
||||
9/36000, 8/36000, 7/36000, 6/36000, 5/36000, 4/36000, 3/36000, 2/36000, 1/36000
|
||||
];
|
||||
Zoom.MAX_IDX_DELTA_PER_TROTTLE = 2;
|
||||
|
||||
Zoom.determineNextFov = function(view, amount) {
|
||||
if (!view.idx)
|
||||
view.idx = Utils.binarySearch(Zoom.LEVELS, view.fov);
|
||||
|
||||
let deltaIdx = amount;
|
||||
view.idx += deltaIdx;
|
||||
|
||||
// clamp to the array indices
|
||||
if (view.idx < 0) {
|
||||
view.idx = 0
|
||||
}
|
||||
|
||||
if (view.idx >= Zoom.LEVELS.length) {
|
||||
view.idx = Zoom.LEVELS.length - 1
|
||||
}
|
||||
|
||||
return Zoom.LEVELS[view.idx];
|
||||
}
|
||||
|
||||
Zoom.prototype.apply = function(options) {
|
||||
let startZoom = options['start'] || this.view.fov;
|
||||
let finalZoom = options['stop'] || undefined;
|
||||
let interpolationDuration = options['duration'] || 1000; // default to 1seconds
|
||||
if (!finalZoom)
|
||||
return;
|
||||
|
||||
this.finalZoom = finalZoom;
|
||||
|
||||
if (!this.isZooming) {
|
||||
this.isZooming = true;
|
||||
|
||||
this.startTime = performance.now();
|
||||
|
||||
this.x1 = 0
|
||||
this.x2 = 1;
|
||||
this.y1 = startZoom;
|
||||
this.y2 = finalZoom;
|
||||
this.m1 = finalZoom - startZoom;
|
||||
this.m2 = 0;
|
||||
|
||||
this.x = this.x1;
|
||||
} else {
|
||||
// find the startTime
|
||||
this.x = (performance.now() - this.startTime) / interpolationDuration;
|
||||
|
||||
let m1 = Zoom.hermiteCubic.fPrime(this.x, this.x1, this.x2, this.y1, this.y2, this.m1, this.m2)
|
||||
let y1 = Zoom.hermiteCubic.f(this.x, this.x1, this.x2, this.y1, this.y2, this.m1, this.m2);
|
||||
this.y1 = y1;
|
||||
this.x1 = this.x;
|
||||
this.x2 = this.x1 + 1;
|
||||
this.y2 = finalZoom;
|
||||
this.m1 = m1;
|
||||
this.m2 = 0;
|
||||
}
|
||||
|
||||
// Initialize current zoom to the current zoom level
|
||||
let interpolatedZoom;
|
||||
let self = this;
|
||||
// Recursive function to perform interpolation for each frame
|
||||
function interpolateFrame() {
|
||||
//console.log('zooming')
|
||||
//fps = 1000 / self.dt;
|
||||
//totalFrames = interpolationDuration * fps; // Total number of frames
|
||||
self.x = ( performance.now() - self.startTime ) / interpolationDuration;
|
||||
// Calculate step size for each frame
|
||||
//stepSize = (desiredZoom - currentZoom) / totalFrames;
|
||||
interpolatedZoom = Zoom.hermiteCubic.f(self.x, self.x1, self.x2, self.y1, self.y2, self.m1, self.m2);
|
||||
// Clamp the interpolation in case it is < 0 for a time
|
||||
if (interpolatedZoom < Zoom.min()) {
|
||||
interpolatedZoom = Zoom.min();
|
||||
}
|
||||
|
||||
// Apply zoom level to map or perform any necessary rendering
|
||||
self.view.setZoom(interpolatedZoom);
|
||||
|
||||
self.fov = interpolatedZoom;
|
||||
|
||||
// Check if interpolation is complete
|
||||
if (self.x >= self.x2 || Math.abs(interpolatedZoom - self.finalZoom) < 1e-4) {
|
||||
self.view.setZoom(self.finalZoom);
|
||||
|
||||
self.isZooming = false;
|
||||
} else {
|
||||
// Request the next frame
|
||||
requestAnimFrame(interpolateFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// Start interpolation by requesting the first frame
|
||||
requestAnimFrame(interpolateFrame);
|
||||
}
|
||||
|
||||
Zoom.max = function() {
|
||||
return Zoom.LEVELS[0];
|
||||
}
|
||||
|
||||
Zoom.min = function() {
|
||||
return Zoom.LEVELS[Zoom.LEVELS.length - 1];
|
||||
}
|
||||
|
||||
Zoom.hermiteCubic = {
|
||||
f: function(x, x1, x2, y1, y2, m1, m2) {
|
||||
let t = (x - x1) / (x2 - x1)
|
||||
let t2 = t*t;
|
||||
let t3 = t2*t;
|
||||
return (1 - 3*t2 + 2*t3) * y1 + (t - 2*t2 + t3) * m1 + (3*t2 - 2*t3) * y2 + (-t2 + t3) * m2
|
||||
},
|
||||
fPrime: function(x, x1, x2, y1, y2, m1, m2) {
|
||||
let t = (x - x1) / (x2 - x1)
|
||||
let t2 = t*t;
|
||||
return (1 / (x2 - x1))*((-6*t+6*t2)*y1 + (1 - 4*t + 3*t2)*m1 + (6*t - 6*t2)*y2 + m2*(3*t2 - 2*t))
|
||||
}
|
||||
}
|
||||
|
||||
return Zoom;
|
||||
})();
|
||||
@@ -699,7 +699,40 @@ export class OverlayStack extends ContextMenu {
|
||||
};
|
||||
|
||||
l.subMenu = [];
|
||||
l.subMenu.push({
|
||||
label: {
|
||||
icon: {
|
||||
url: searchIconImg,
|
||||
monochrome: true,
|
||||
tooltip: {content: 'Find a specific survey <br /> in our database...', position: { direction: 'bottom' }},
|
||||
cssStyle: {
|
||||
cursor: 'help',
|
||||
},
|
||||
},
|
||||
content: 'More...'
|
||||
},
|
||||
action(o) {
|
||||
o.stopPropagation();
|
||||
o.preventDefault();
|
||||
|
||||
self._hide();
|
||||
|
||||
self.hipsBox = new HiPSSelectorBox(self.aladin)
|
||||
|
||||
self.hipsBox.attach((HiPSId) => {
|
||||
self.aladin.setOverlayImageLayer(HiPSId, layer.layer);
|
||||
self.show();
|
||||
});
|
||||
|
||||
self.hipsBox._show({
|
||||
position: self.position,
|
||||
})
|
||||
|
||||
self.mode = 'hips';
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
for(const [id, ll] of defaultLayers) {
|
||||
backgroundUrl = OverlayStack.previewImagesUrl[ll.name];
|
||||
if (!backgroundUrl) {
|
||||
@@ -732,41 +765,6 @@ export class OverlayStack extends ContextMenu {
|
||||
})
|
||||
}
|
||||
|
||||
l.subMenu.push({
|
||||
label: {
|
||||
icon: {
|
||||
url: searchIconImg,
|
||||
monochrome: true,
|
||||
tooltip: {content: 'Find a specific survey <br /> in our database...', position: { direction: 'top' }},
|
||||
cssStyle: {
|
||||
cursor: 'help',
|
||||
},
|
||||
},
|
||||
content: 'More...'
|
||||
},
|
||||
action(o) {
|
||||
o.stopPropagation();
|
||||
o.preventDefault();
|
||||
|
||||
self._hide();
|
||||
|
||||
self.hipsBox = new HiPSSelectorBox(self.aladin)
|
||||
|
||||
self.hipsBox.attach(
|
||||
(HiPSId) => {
|
||||
self.aladin.setOverlayImageLayer(HiPSId, layer.layer);
|
||||
self.show();
|
||||
}
|
||||
);
|
||||
|
||||
self.hipsBox._show({
|
||||
position: self.position,
|
||||
})
|
||||
|
||||
self.mode = 'hips';
|
||||
}
|
||||
})
|
||||
|
||||
l.action = (o) => {
|
||||
let oldLayerClassName = 'a' + self.aladin.getSelectedLayer().replace(/[.\/ ]/g, '')
|
||||
self.el.querySelector('.' + oldLayerClassName).style.removeProperty('border')
|
||||
|
||||
@@ -15,28 +15,4 @@ Element.prototype.swap = function (node) {
|
||||
|
||||
// Move `node` to before the sibling of `this`
|
||||
parent.insertBefore(node, sibling);
|
||||
};
|
||||
|
||||
export let Utils = {}
|
||||
/**
|
||||
* Append el to target
|
||||
*
|
||||
* target must be an DOM Element/Node
|
||||
*
|
||||
* @API
|
||||
*
|
||||
* @param el: el can be a Widget or Element object. Otherwise it is considered as text
|
||||
* @param target: target must be an DOM Element/Node
|
||||
*
|
||||
*/
|
||||
Utils.binarySearch = function(array, value) {
|
||||
var low = 0,
|
||||
high = array.length;
|
||||
|
||||
while (low < high) {
|
||||
var mid = (low + high) >>> 1;
|
||||
if (array[mid] < value) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
};
|
||||
@@ -30,7 +30,6 @@
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
import { Utils } from "../Utils.js";
|
||||
import { DOMElement } from "./Widget.js";
|
||||
|
||||
export class Table extends DOMElement {
|
||||
|
||||
@@ -304,7 +304,7 @@ export class DOMElement {
|
||||
attachTo(target, position = 'beforeend') {
|
||||
if(target) {
|
||||
if (typeof position === 'number') {
|
||||
target.insertChildAtIndex(this.element(), position)
|
||||
target.insertBefore(this.element(), target.childNodes[position]);
|
||||
} else {
|
||||
target.insertAdjacentElement(position, this.element());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user