Compare commits

..

39 Commits

Author SHA1 Message Date
Matthieu Baumann
a54ef4510a fix polyfill in sin proj 2023-09-22 16:59:51 +02:00
Matthieu Baumann
7947817694 Polyline: add hpx code to remove drawing lines in between 2 different collinion zone 2023-09-21 17:05:19 +02:00
szpetny
79ecda33ca all sky with polygons HPX MOL - examples added 2023-09-21 09:54:18 +02:00
szpetny
e622207145 A proposal of a new feature - let the polygon to be filled in with a given color with given transparency 2023-09-21 09:50:51 +02:00
szpetny
43a8bf0e6e examples fixed 2023-09-20 19:28:42 +02:00
szpetny
01aff09511 expose mouse coords in callbacks for source/footprint click and hover 2023-09-20 19:28:42 +02:00
Matthieu Baumann
7a52a9f962 Fix #123, difficulty to get the radius in pixel of a circle at the edge of a projection. My solution: I project 4 vertices on the circle lying in the cardinal points N, S, E, W and I take as radius the minimal distance between the circle center and one these vertices lying on the circle 2023-09-14 06:20:07 +02:00
Matthieu Baumann
3bda0fcd75 Merge pull request #118 from szpetny/draw-function-fixes
Small changed regarding drawing a footprint
2023-08-31 16:55:53 +02:00
Matthieu Baumann
d6e753ee85 Merge pull request #117 from szpetny/read-only-catalog
read only catalog option
2023-08-28 15:42:18 +02:00
szpetny
98b0d0dff6 setOverlay added to Footprint bc draw function fails when it is lacking,
improvement - reading lineWidth from overlay if not specified in shape
2023-08-22 16:29:06 +02:00
szpetny
5364eaf124 read only catalog option 2023-08-22 16:08:14 +02:00
Matthieu Baumann
c35c20d241 update the deploy 2023-08-22 14:50:46 +02:00
Matthieu Baumann
be619b3e8c fix relMouseCoords when rightclicking to make the context menu appear again 2023-08-22 14:42:00 +02:00
Matthieu Baumann
62633d01bc fix of a weird resize problem occuring when accessing the gridCanvas. This has been reported by Robin MENEUST from obspm 2023-08-15 18:25:49 +02:00
Matthieu Baumann
bc1096fce3 Aladin lite: v3.2.0
Features:
- The use of vite as a project manager
- Enhance the MOC rendering perf and add possibility to draw the perimeter
- Many fixes such as the footprints rendering for all sky projections
- A line rasterizer enabling the thickness' grid line changes
2023-08-13 14:10:44 +02:00
Matthieu Baumann
7d5696228d remove engines in package json 2023-08-12 19:14:44 +02:00
Matthieu Baumann
08699a9bd5 Merge branch 'develop' into develop 2023-08-12 19:00:28 +02:00
Matthieu Baumann
fc6a09e373 Fix rebase 2023-08-12 15:26:54 +02:00
Matthieu Baumann
a8a86a2952 Merge branch 'develop' into develop 2023-08-12 15:18:38 +02:00
Matthieu Baumann
cc958bfa2d Corrected ESASky link in README
Redo PR #104 commit directly on develop

Co-authored-by: imbasimba <https://www.henriknorman.com>
2023-08-12 15:06:13 +02:00
Matthieu Baumann
6c4ddce6b0 Merge pull request #113 from cds-astro/features/lineRasterizer
Features/line rasterizer
2023-08-12 15:04:17 +02:00
onekiloparsec
c1b2bd24b9 Reverting the insertion of /.idea into the gitignore.
It can be anaged individually.
2023-08-03 13:31:34 +02:00
Manon
46573a23da fix draw 2023-08-01 10:08:15 +02:00
Cédric Foellmi
121f4345bc Update src/js/gui/ContextMenu.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:01:47 +02:00
Cédric Foellmi
0665f2b65f Update src/js/View.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:01:38 +02:00
Cédric Foellmi
a58fb1dd8a Update src/js/View.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:01:29 +02:00
Cédric Foellmi
466472a1a7 Update src/js/View.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:01:14 +02:00
Cédric Foellmi
540f4e33be Update src/js/View.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:01:04 +02:00
Cédric Foellmi
0b92b6d1db Update src/js/GenericPointer.js
Fixed the missing canvas parameter of the refactored `relMouseCoords` function.

Co-authored-by: Matthieu Baumann <baumannmatthieu0@gmail.com>
2023-07-19 04:00:38 +02:00
Cédric Foellmi
06dcc126f9 Fixed import
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
04e552b7c3 Making a first test passing!
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
1bee9c8b77 Ignoring .idea (PyCharm/WebStorm)
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
c77f2aeda8 Making a dedicated dependency-free Constants file to avoid Utils import Aladin.js!
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
57c1b8423d Linting
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
ebf2d06f31 Using the Utils methods also in the examples
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
5d0ec40612 Fixing imports
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
82b2eb0423 Using the new Utils method
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:16 +02:00
Cédric Foellmi
2dc6f17c7d Renaming Utils.js into Utils.ts and correcting related imports.
Note that the content of Utils.ts has also been changed. More pecisely:

- `relMouseCoords` has been set as a function of the Utils object, to avoid attaching it to canvas prototype. This was very custom way of doing, and makes testing very complicated to run, while providing no real value.

- Removed the jQuery dependency and made `urlParam` a function of the Utils object.

- Added some types when possible / easy. But TS already reveal the misuse of some function sur as `parseInt` and `parseFloat` (which act on strings, not numbers)

Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:11:15 +02:00
Cédric Foellmi
402e270015 Introducing typescript & vitest
In order to push upward the code quailty of the Javascript code, I propose to introduce Typescript and Vitest for writing unit tests.

This first commit simply introduce the right dependencies and configuration.

Note the presence of “happy-dom” dev dep for manipulating the window object (which is involved in the first test written on Utils).

Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
2023-07-18 12:07:21 +02:00
53 changed files with 24422 additions and 695 deletions

View File

@@ -32,4 +32,6 @@ jobs:
run: |
npm run build
- name: "Run some tests"
run: npm test
run: |
npm run test:build
npm run test:unit

4
.gitignore vendored
View File

@@ -8,6 +8,6 @@ package-lock.json
src/core/target/
src/core/Cargo.lock
aladin-lite-3.1.0.tgz
aladin-lite*.tgz
.vscode
.vscode

View File

@@ -6,7 +6,7 @@ Aladin Lite is a Web application which enables HiPS visualization from the brows
See [A&A 578, A114 (2015)](https://arxiv.org/abs/1505.02291) and [IVOA HiPS Recommendation](http://ivoa.net/documents/HiPS/index.html) for more details about the HiPS standard.
Aladin Lite is built to be easily embeddable in any web page. It powers astronomical portals like [ESASky](https://almascience.eso.org/asax/), [ESO Science Archive portal](http://archive.eso.org/scienceportal/) and [ALMA Portal](https://almascience.eso.org/asax/).
Aladin Lite is built to be easily embeddable in any web page. It powers astronomical portals like [ESASky](https://sky.esa.int/), [ESO Science Archive portal](http://archive.eso.org/scienceportal/) and [ALMA Portal](https://almascience.eso.org/asax/).
More details on [Aladin Lite documentation page](http://aladin.u-strasbg.fr/AladinLite/doc/).

View File

@@ -6,11 +6,12 @@ ssh $USER_ALADIN@aladin 'sg hips -c "mkdir -p /home/matthieu.baumann/al-tmp && r
# For compatibility with the docs, rename the UMD file into aladin.js
scp dist/aladin.umd.cjs $USER_ALADIN@aladin:~/al-tmp/aladin.js
# Copy the tgz
cp aladin-l*.tgz aladin-lite.tgz
mv aladin-l*.tgz aladin-lite.tgz
scp aladin-lite.tgz $USER_ALADIN@aladin:~/al-tmp
ssh $USER_ALADIN@aladin "sg hips -c 'rm -rf /home/thomas.boch/AladinLite/www/api/v3/$DATEUPLOAD &&
mkdir -p /home/thomas.boch/AladinLite/www/api/v3/$DATEUPLOAD &&
cp /home/matthieu.baumann/al-tmp/* /home/thomas.boch/AladinLite/www/api/v3/$DATEUPLOAD &&
rm -rf /home/thomas.boch/AladinLite/www/api/v3/latest &&
ln -s /home/thomas.boch/AladinLite/www/api/v3/$DATEUPLOAD /home/thomas.boch/AladinLite/www/api/v3/latest'"
ln -s /home/thomas.boch/AladinLite/www/api/v3/$DATEUPLOAD /home/thomas.boch/AladinLite/www/api/v3/latest &&
ln -s /home/thomas.boch/AladinLite/www/api/v3/latest/aladin-lite.tgz /home/thomas.boch/AladinLite/www/api/v3/latest/AladinLiteAssets.tar.gz'"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {survey: "CDS/P/DSS2/color", target: 'M 1', fov: 0.2, showContextMenu: true, fullScreen: true});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3});
aladin.addOverlay(overlay);
overlay.add(A.circle(83.66067, 22.03081, 40.0, {color: 'cyan'})); // radius in degrees
aladin.on("footprintClicked", (footprint) => {
console.log("footprint clicked catched", footprint)
})
aladin.on("objectClicked", (object) => {
console.log("object clicked catched", object)
})
aladin.on("footprintHovered", (footprint) => {
console.log("footprint hovered catched", footprint)
})
aladin.on("objectHoveredStop", (object) => {
console.log("Object hovered stopped", object)
})
});
</script>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<!--<link rel="stylesheet" href="./layers.css" />-->
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: 'P/DSS2/red', target: 'M50', fov: 0.7});
var cat1 = A.catalog({readOnly: true});
aladin.addCatalog(cat1);
cat1.addSources(A.source(105.69239256, -8.45235969));
cat1.addSources(A.source(105.70779763, -8.31350997));
cat1.addSources(A.source(105.74242906, -8.34776709));
var cat2 = A.catalog({readOnly: false});
aladin.addCatalog(cat2);
cat2.addSources(A.source(105.79239256, -8.45235969));
cat2.addSources(A.source(105.90779763, -8.31350997));
cat2.addSources(A.source(105.54242906, -8.34776709));
});
</script>
</body>
</html>

View File

@@ -14,7 +14,7 @@
<!--link rel="stylesheet" href="css/grids-responsive-min.css"-->
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/buttons.css">
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/buttons-core.css">
<!--meta name="viewport" content="initial-scale=1.0, user-scalable=no"-->
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
@@ -81,15 +81,15 @@
<div id="coo_epoca">
<a class="pure-button nav-button nav-goto" href="#">De Epoca</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
</div>
<div id="coo_legende">
<a class="pure-button nav-button nav-goto" href="#">Légende</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
</div>
<div id="coo_orion">
<a class="pure-button nav-button nav-goto" href="#">Orion</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
</div>
<div id="coo_magellan">
<a class="pure-button nav-button nav-goto" href="#">Magellan</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
@@ -97,7 +97,7 @@
<div id="coo_halley">
<a class="pure-button nav-button nav-goto" href="#">Halley</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
</div>
</div>
<style type="text/css"> .aladin-reticleColor { color: rgb(178, 50, 178); font-weight:bold;} </style>
@@ -106,27 +106,27 @@
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
.aladin-zoomControl {
top: 10% !important;
left: unset !important;
right: 4px !important;
}
.aladin-zoomControl a {
font-size: 24px !important;
padding: 22px !important;
}
#aladin{
flex: 1 1 0;
}
#explain {
padding: 4px;
top: 30%;
@@ -134,51 +134,51 @@
font-size: 11pt;
overflow: scroll;
}
#explain tbody tr:nth-child(even) {
background-color: #ffffff;
}
#explain tbody tr:nth-child(odd) {
background-color: #ccdaeb;
}
#layersControlLeft {
padding: 10px;
right: unset;
left: 4px;
top: 20vh;
}
#layersCL2 {
padding: 10px;
right: unset;
left: 4px;
top: 90vh;
}
#layersControlRight {
padding: 4px;
left: unset;
right: 4px;
top: 25vh;
}
#layersControlLeft, #layersControlRight, #layersCL2 input {
margin-right: 5px;
}
.img-hips {
padding: 5px;
margin: 5px;
}
#opacity-slider {
-webkit-appearance: none !important; /* Override default CSS styles */
width: 220px;
height: 25px;
}
#opacity-slider::-webkit-slider-thumb {
-webkit-appearance: none !important; /* Override default look */
appearance: none;
@@ -187,18 +187,18 @@
background: #4CAF50; /* Green background */
cursor: pointer; /* Cursor on hover */
}
#opacity-slider::-moz-range-thumb {
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
background: #4CAF50; /* Green background */
cursor: pointer; /* Cursor on hover */
}
.aladin-box {
font-size: 12px !important;
}
#calibCircle {
position: fixed;
border: 8px solid red;
@@ -210,16 +210,16 @@
z-index: 1000;
pointer-events: none;
}
.pure-table {
font-size: small;
}
.catcoro {
display: inline;
vertical-align: middle;
}
.coro-star {
vertical-align: middle;
}
@@ -227,6 +227,8 @@
<script type="module">
import A from '../src/js/A.js';
import {Utils} from '../src/js/Utils';
A.init.then(() => {
var hipsDir="http://alasky.u-strasbg.fr/CDS_P_Coronelli";
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
@@ -358,7 +360,7 @@
});
// listen to click on objects
aladin.on('objectClicked', function(source) {
aladin.on('objectClicked', function(source, xyMouseCoords) {
var html = '<table class="pure-table">';
if (curSelectedSource != null) {
@@ -495,7 +497,7 @@
deleteOverlayTimeout = undefined;
}
isDrawing = true;
points.push([drawOverlayCanvas.relMouseCoords(e)]);
points.push([Utils.relMouseCoords(drawOverlayCanvas.imageCanvas, e)]);
});
@@ -504,10 +506,10 @@
e.preventDefault();
drawOverlayCtx.clearRect(0, 0, drawOverlayCtx.canvas.width, drawOverlayCtx.canvas.height);
points[points.length-1].push(drawOverlayCanvas.relMouseCoords(e));
points[points.length-1].push(Utils.relMouseCoords(drawOverlayCanvas.imageCanvas, e));
drawOverlayCtx.beginPath();
for (var k=0; k<points.length; k++) {
drawOverlayCtx.moveTo(points[k][0].x, points[k][0].y);
for (var i = 1; i < points[k].length; i++) {

View File

@@ -17,9 +17,10 @@
var msg;
// define function triggered when a source is hovered
aladin.on('objectHovered', function(object) {
aladin.on('objectHovered', function(object, xyMouseCoords) {
if (object) {
msg = 'You hovered object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec;
msg = 'You hovered object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec + '; mouse coords - x: '
+ xyMouseCoords.x + ', y: ' + xyMouseCoords.y;
}
else {
msg = 'No object hovered';
@@ -27,20 +28,22 @@
$('#infoDiv').html(msg);
});
aladin.on('objectHoveredStop', function(object) {
aladin.on('objectHoveredStop', function(object, xyMouseCoords) {
if (object) {
msg = 'You stopped hove object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec;
msg = 'You stopped hove object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec + '; mouse coords - x: '
+ xyMouseCoords.x + ', y: ' + xyMouseCoords.y;
}
$('#infoDiv').html(msg);
});
// define function triggered when an object is clicked
var objClicked;
aladin.on('objectClicked', function(object) {
aladin.on('objectClicked', function(object, xyMouseCoords) {
if (object) {
objClicked = object;
object.select();
msg = 'You clicked object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec;
object.select();
msg = 'You clicked object ' + object.data.name + ' located at ' + object.ra + ', ' + object.dec + '; mouse coords - x: '
+ xyMouseCoords.x + ', y: ' + xyMouseCoords.y;
}
else {
objClicked.deselect();
@@ -51,4 +54,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -0,0 +1,26 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {fov: 122, showContextMenu: true, fullScreen: true});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3});
aladin.addOverlay(overlay);
overlay.addFootprints([A.polygon(
[[264.375,-35.68533471265207], [258.75,-30.000000000000018], [264.375,-24.624318352164085], [270,-30.000000000000018]],
{color: '#808080', fillColor: '#808080', opacity: .4, lineWidth: 1, fill: true})]);
aladin.gotoRaDec(264.375,-24.624318352164085);
});
</script>
</body>
</html>

View File

@@ -21,17 +21,17 @@
]);
overlay.add(A.circle(83.66067, 22.03081, 0.04, {color: 'cyan'})); // radius in degrees
aladin.on("footprintClicked", (footprint) => {
console.log("footprint clicked catched", footprint)
aladin.on("footprintClicked", (footprint, xyMouseCoords) => {
console.log("footprint clicked catched: ", footprint, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
aladin.on("objectClicked", (object) => {
console.log("object clicked catched", object)
aladin.on("objectClicked", (object, xyMouseCoords) => {
console.log("object clicked catched: ", object, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
aladin.on("footprintHovered", (footprint) => {
console.log("footprint hovered catched", footprint)
aladin.on("footprintHovered", (footprint, xyMouseCoords) => {
console.log("footprint hovered catched: ", footprint, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
aladin.on("objectHoveredStop", (object) => {
console.log("Object hovered stopped", object)
aladin.on("objectHoveredStop", (object, xyMouseCoords) => {
console.log("Object hovered stopped: ", object, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 100, {onClick: 'showTable', limit: 1000});

View File

@@ -88,7 +88,7 @@
});
// listen to click on objects
aladin.on('objectClicked', function (source) {
aladin.on('objectClicked', function (source, xyMouseCoords) {
var html = '<table class="pure-table">';
if (curSelectedSource != null) {
@@ -142,4 +142,4 @@
});
});
</script>
</html>
</html>

View File

@@ -2,7 +2,7 @@
"homepage": "https://aladin.u-strasbg.fr/",
"name": "aladin-lite",
"type": "module",
"version": "3.1.0",
"version": "3.2.0",
"description": "An astronomical HiPS visualizer in the browser",
"author": "Thomas Boch and Matthieu Baumann",
"license": "GPL-3",
@@ -33,22 +33,26 @@
],
"scripts": {
"wasm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
"predeploy": "npm run build && npm pack",
"deploy": "./deploy-dbg.sh",
"predeploy": "npm run build && rm -rf aladin-lite.tgz && npm pack",
"deploy": "./deploy.sh",
"build": "npm run wasm && vite build && cp examples/index.html dist/index.html",
"dev": "npm run build && vite",
"serve": "npm run dev",
"preview": "vite preview",
"test": "cd src/core && cargo test --release --features webgl2"
"test:build": "cd src/core && cargo test --release --features webgl2",
"test:unit": "vitest run"
},
"devDependencies": {
"npm": "^8.19.2",
"happy-dom": "^8.9.0",
"npm": "^9.8.1",
"typescript": "^5.0.4",
"vite": "^4.3.8",
"vite-plugin-css-injected-by-js": "^3.1.1",
"vite-plugin-glsl": "^1.1.2",
"vite-plugin-top-level-await": "^1.3.1",
"vite-plugin-wasm": "^3.2.2",
"vite-plugin-wasm-pack": "^0.1.12"
"vite-plugin-wasm-pack": "^0.1.12",
"vitest": "^0.32.2"
},
"dependencies": {
"autocompleter": "^6.1.3",

View File

@@ -3,7 +3,7 @@ name = "aladin-lite"
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
license = "BSD-3-Clause"
repository = "https://github.com/cds-astro/aladin-lite"
version = "3.1.1"
version = "3.2.0"
authors = ["baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr"]
edition = "2018"

View File

@@ -18,6 +18,7 @@ use crate::{
tile_fetcher::TileFetcherQueue,
time::DeltaTime,
};
use al_core::{info, inforec, log};
use wasm_bindgen::prelude::*;
@@ -601,7 +602,6 @@ impl App {
let is_tile_root = tile.cell().depth() == delta_depth;
let _depth = tile.cell().depth();
//al_core::info!("is root tile", depth, is_tile_root);
// do not perform tex_sub costly GPU calls while the camera is zooming
if is_tile_root || included_or_near_coverage {
let is_missing = tile.missing();
@@ -1258,6 +1258,7 @@ impl App {
pub(crate) fn resize(&mut self, width: f32, height: f32) {
self.camera.set_screen_size(width, height, &self.projection);
self.camera
.set_aperture(self.camera.get_aperture(), &self.projection);
// resize the view fbo

View File

@@ -10,7 +10,7 @@ use super::{fov::FieldOfView, view_hpx_cells::ViewHpxCells};
use crate::healpix::cell::HEALPixCell;
use crate::healpix::coverage::HEALPixCoverage;
use crate::math::{projection::coo_space::XYZWModel, projection::domain::sdf::ProjDef};
use al_core::{info, inforec, log};
use cgmath::{Matrix4, Vector2};
pub struct CameraViewPort {
@@ -265,14 +265,14 @@ impl CameraViewPort {
.unwrap_abort();
// grid canvas
let document = web_sys::window().unwrap_abort().document().unwrap_abort();
/*let document = web_sys::window().unwrap_abort().document().unwrap_abort();
let grid_canvas = document
// Inside it, retrieve the canvas
.get_elements_by_class_name("aladin-gridCanvas")
.get_with_index(0)
.unwrap_abort()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap_abort();
.unwrap_abort();*/
canvas
.style()
@@ -282,19 +282,19 @@ impl CameraViewPort {
.style()
.set_property("height", &format!("{}px", height))
.unwrap_abort();
grid_canvas
/*grid_canvas
.style()
.set_property("width", &format!("{}px", width))
.unwrap_abort();
grid_canvas
.style()
.set_property("height", &format!("{}px", height))
.unwrap_abort();
.unwrap_abort();*/
canvas.set_width(self.width as u32);
canvas.set_height(self.height as u32);
grid_canvas.set_width(self.width as u32);
grid_canvas.set_height(self.height as u32);
//grid_canvas.set_width(self.width as u32);
//grid_canvas.set_height(self.height as u32);
// Once the canvas size is changed, we have to set the viewport as well
self.gl
@@ -306,7 +306,6 @@ impl CameraViewPort {
self.height = (height as f32) * self.dpi;
self.aspect = width / height;
// Compute the new clip zoom factor
self.compute_ndc_to_clip_factor(projection);
@@ -316,11 +315,13 @@ impl CameraViewPort {
&self.w2m,
projection,
);
let proj_area = projection.get_area();
self.is_allsky = !proj_area.is_in(&math::projection::ndc_to_clip_space(
&Vector2::new(-1.0, -1.0),
self,
));
// Update the size of the canvas
self.set_canvas_size(width, height);
// Once it is done, recompute the scissor
@@ -404,6 +405,7 @@ impl CameraViewPort {
self.fov
.set_aperture(&self.ndc_to_clip, self.clip_zoom_factor, &self.w2m, proj);
let proj_area = proj.get_area();
self.is_allsky = !proj_area.is_in(&math::projection::ndc_to_clip_space(
&Vector2::new(-1.0, -1.0),

View File

@@ -14,8 +14,6 @@ use crate::renderable::Renderer;
use crate::ProjectionType;
use al_api::color::ColorRGBA;
use al_api::grid::GridCfg;
use crate::grid::label::Label;
@@ -231,7 +229,7 @@ impl ProjetedGrid {
rot,
} in labels
{
let position = position.cast::<f32>().unwrap_abort() * dpi;
let position = position.cast::<f32>().unwrap_abort();
self.text_renderer
.add_label(&content, &position, cgmath::Rad(*rot as f32))?;
}

View File

@@ -1,21 +1,20 @@
use web_sys::CanvasRenderingContext2d;
use super::Renderer;
use web_sys::CanvasRenderingContext2d;
pub struct TextRenderManager {
// The text canvas
canvas: HtmlCanvasElement,
ctx: CanvasRenderingContext2d,
color: JsValue,
font_size: u32,
font_size: u32,
}
use cgmath::{Rad, Vector2};
use wasm_bindgen::JsValue;
use crate::camera::CameraViewPort;
use al_api::color::{ColorRGBA, ColorRGB};
use web_sys::{HtmlCanvasElement};
use al_api::color::{ColorRGB, ColorRGBA};
use web_sys::HtmlCanvasElement;
use crate::Abort;
use wasm_bindgen::JsCast;
@@ -34,10 +33,11 @@ impl TextRenderManager {
.get_context("2d")
.unwrap_abort()
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap_abort();
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap_abort();
let color = JsValue::from_str("#00ff00");
let font_size = 30;
let font_size = 15;
Ok(Self {
font_size,
color,
@@ -47,7 +47,11 @@ impl TextRenderManager {
}
pub fn set_color(&mut self, color: &ColorRGB) {
let hex = al_api::color::Color::rgbToHex((color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8);
let hex = al_api::color::Color::rgbToHex(
(color.r * 255.0) as u8,
(color.g * 255.0) as u8,
(color.b * 255.0) as u8,
);
self.color = JsValue::from_str(&hex);
}
@@ -60,9 +64,10 @@ impl TextRenderManager {
text: &str,
screen_pos: &Vector2<f32>,
angle: A,
) -> Result<(), JsValue>{
) -> Result<(), JsValue> {
self.ctx.save();
self.ctx.translate(screen_pos.x as f64, screen_pos.y as f64)?;
self.ctx
.translate(screen_pos.x as f64, screen_pos.y as f64)?;
let rot: Rad<f32> = angle.into();
self.ctx.rotate(rot.0 as f64)?;
@@ -71,31 +76,44 @@ impl TextRenderManager {
self.ctx.fill_text(text, 0.0, 0.0)?;
self.ctx.restore();
Ok(())
}
pub fn draw(&mut self, _camera: &CameraViewPort, _color: &ColorRGBA, _scale: f32) -> Result<(), JsValue> {
pub fn draw(
&mut self,
_camera: &CameraViewPort,
_color: &ColorRGBA,
_scale: f32,
) -> Result<(), JsValue> {
Ok(())
}
pub fn clear_text_canvas(&mut self) {
self.ctx.clear_rect(0_f64, 0_f64, self.canvas.width() as f64, self.canvas.height() as f64);
self.ctx.clear_rect(
0_f64,
0_f64,
self.canvas.width() as f64,
self.canvas.height() as f64,
);
}
}
impl Renderer for TextRenderManager {
fn begin(&mut self) {
self.ctx = self.canvas
self.ctx = self
.canvas
.get_context("2d")
.unwrap_abort()
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap_abort();
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap_abort();
self.clear_text_canvas();
// reset the font and color
self.ctx.set_font(&format!("{}px verdana, sans-serif", self.font_size));
self.ctx
.set_font(&format!("{}px verdana, sans-serif", self.font_size));
self.ctx.set_fill_style(&self.color);
}

View File

@@ -29,7 +29,7 @@
*****************************************************************************/
import { View } from "./View.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { Overlay } from "./Overlay.js";
import { Logger } from "./Logger.js";
import { ProgressiveCat } from "./ProgressiveCat.js";
@@ -580,10 +580,10 @@ export let Aladin = (function () {
}
// Delay the fixLayoutDimensions layout for firefox
setTimeout(function () {
/*setTimeout(function () {
self.view.fixLayoutDimensions();
}, 1000);
*/
// force call to zoomChanged callback
var fovChangedFn = self.callbacksByEventName['zoomChanged'];
(typeof fovChangedFn === 'function') && fovChangedFn(self.view.fov);
@@ -594,34 +594,34 @@ export let Aladin = (function () {
Aladin.prototype.getOptionsFromQueryString = function () {
var options = {};
var requestedTarget = $.urlParam('target');
var requestedTarget = Utils.urlParam('target');
if (requestedTarget) {
options.target = requestedTarget;
}
var requestedFrame = $.urlParam('frame');
var requestedFrame = Utils.urlParam('frame');
if (requestedFrame && CooFrameEnum[requestedFrame]) {
options.frame = requestedFrame;
}
var requestedSurveyId = $.urlParam('survey');
var requestedSurveyId = Utils.urlParam('survey');
if (requestedSurveyId && ImageSurvey.getSurveyInfoFromId(requestedSurveyId)) {
options.survey = requestedSurveyId;
}
var requestedZoom = $.urlParam('zoom');
var requestedZoom = Utils.urlParam('zoom');
if (requestedZoom && requestedZoom > 0 && requestedZoom < 180) {
options.zoom = requestedZoom;
}
var requestedShowreticle = $.urlParam('showReticle');
var requestedShowreticle = Utils.urlParam('showReticle');
if (requestedShowreticle) {
options.showReticle = requestedShowreticle.toLowerCase() == 'true';
}
var requestedCooFrame = $.urlParam('cooFrame');
var requestedCooFrame = Utils.urlParam('cooFrame');
if (requestedCooFrame) {
options.cooFrame = requestedCooFrame;
}
var requestedFullscreen = $.urlParam('fullScreen');
var requestedFullscreen = Utils.urlParam('fullScreen');
if (requestedFullscreen !== undefined) {
options.fullScreen = requestedFullscreen;
}
@@ -1786,4 +1786,4 @@ Aladin.prototype.displayJPG = Aladin.prototype.displayPNG = function (url, optio
Aladin.prototype.setReduceDeformations = function (reduce) {
this.reduceDeformations = reduce;
this.view.requestRedraw();
}
}

View File

@@ -22,16 +22,16 @@
/******************************************************************************
* Aladin Lite project
*
*
* File Catalog
*
*
* Author: Thomas Boch[CDS]
*
*
*****************************************************************************/
import { Source } from "./Source.js"
import { Color } from "./Color.js"
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { Coo } from "./libs/astro/coo.js";
import { VOTable } from "./vo/VOTable.js";
import { ALEvent } from "./events/ALEvent.js";
@@ -57,6 +57,7 @@ export let Catalog = (function() {
this.shape = options.shape || "square";
this.maxNbSources = options.limit || undefined;
this.onClick = options.onClick || undefined;
this.readOnly = options.readOnly || false;
this.raField = options.raField || undefined; // ID or name of the field holding RA
this.decField = options.decField || undefined; // ID or name of the field holding dec
@@ -82,10 +83,10 @@ export let Catalog = (function() {
this.displayLabel = false;
}
}
this.selectionColor = '#00ff00';
// create this.cacheCanvas
// 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)
this.updateShape(options);
@@ -104,7 +105,7 @@ export let Catalog = (function() {
this.isShowing = true;
};
Catalog.createShape = function(shapeName, color, sourceSize) {
if (shapeName instanceof Image || shapeName instanceof HTMLCanvasElement) { // in this case, the shape is already created
return shapeName;
@@ -119,7 +120,7 @@ export let Catalog = (function() {
ctx.moveTo(sourceSize/2., 0);
ctx.lineTo(sourceSize/2., sourceSize);
ctx.stroke();
ctx.moveTo(0, sourceSize/2.);
ctx.lineTo(sourceSize, sourceSize/2.);
ctx.stroke();
@@ -128,7 +129,7 @@ export let Catalog = (function() {
ctx.moveTo(0, 0);
ctx.lineTo(sourceSize-1, sourceSize-1);
ctx.stroke();
ctx.moveTo(sourceSize-1, 0);
ctx.lineTo(0, sourceSize-1);
ctx.stroke();
@@ -160,10 +161,10 @@ export let Catalog = (function() {
ctx.lineTo(1, 1);
ctx.stroke();
}
return c;
};
// find RA, Dec fields among the given fields
//
@@ -182,7 +183,7 @@ export let Catalog = (function() {
if (Utils.isInt(raField) && raField<fields.length) { // raField can be given as an index
raFieldIdx = raField;
break;
}
}
if ( (field.ID && field.ID===raField) || (field.name && field.name===raField)) {
raFieldIdx = l;
break;
@@ -195,7 +196,7 @@ export let Catalog = (function() {
if (Utils.isInt(decField) && decField<fields.length) { // decField can be given as an index
decFieldIdx = decField;
break;
}
}
if ( (field.ID && field.ID===decField) || (field.name && field.name===decField)) {
decFieldIdx = l;
break;
@@ -218,7 +219,7 @@ export let Catalog = (function() {
}
}
}
if ( ! decFieldIdx) {
if (field.ucd) {
var ucd = $.trim(field.ucd.toLowerCase());
@@ -236,7 +237,7 @@ export let Catalog = (function() {
var field = fields[l];
var name = field.name || field.ID || '';
name = name.toLowerCase();
if ( ! raFieldIdx) {
if (name.indexOf('ra')==0 || name.indexOf('_ra')==0 || name.indexOf('ra(icrs)')==0 || name.indexOf('_ra')==0 || name.indexOf('alpha')==0) {
raFieldIdx = l;
@@ -250,7 +251,7 @@ export let Catalog = (function() {
continue;
}
}
}
}
@@ -262,8 +263,8 @@ export let Catalog = (function() {
return [raFieldIdx, decFieldIdx];
};
Catalog.parseFields = function(fields, raField, decField) {
// This votable is not an obscore one
let [raFieldIdx, decFieldIdx] = findRADecFields(fields, raField, decField);
@@ -379,13 +380,13 @@ export let Catalog = (function() {
this.selectSize = this.sourceSize + 2;
this.cacheCanvas = Catalog.createShape(this.shape, this.color, this.sourceSize);
this.cacheCanvas = Catalog.createShape(this.shape, this.color, this.sourceSize);
this.cacheSelectCanvas = Catalog.createShape(this.shape, this.selectionColor, this.selectSize);
this.cacheHoverCanvas = Catalog.createShape(this.shape, this.hoverColor, this.sourceSize);
this.reportChange();
};
// API
Catalog.prototype.addSources = function(sources) {
// make sure we have an array and not an individual source
@@ -507,7 +508,7 @@ export let Catalog = (function() {
if (! this.sources) {
return;
}
for (var k=0; k<this.sources.length; k++) {
this.sources[k].select();
}
@@ -702,7 +703,7 @@ export let Catalog = (function() {
Catalog.prototype.reportChange = function() {
this.view && this.view.requestRedraw();
};
Catalog.prototype.show = function() {
if (this.isShowing) {
return;
@@ -715,7 +716,7 @@ export let Catalog = (function() {
this.reportChange();
};
Catalog.prototype.hide = function() {
if (! this.isShowing) {
return;

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
import { Overlay } from "./Overlay.js";
@@ -163,31 +163,48 @@ export let Circle = (function() {
x: centerXyview[0],
y: centerXyview[1],
};
// compute value of radius in pixels in current projection
var ra = this.centerRaDec[0];
var dec = this.centerRaDec[1] + (ra>0 ? - this.radiusDegrees : this.radiusDegrees);
// First check, the point in the circle is defined
let circlePtXyView = AladinUtils.radecToViewXy(ra, dec, view);
if (!circlePtXyView) {
// the circle border goes out of the projection
// we do not draw it
return;
let hidden = true;
var ra, dec, vertOnCircle, dx, dy;
if (view.fov > 90) {
this.radius = Number.POSITIVE_INFINITY;
// Project 4 points lying on the circle and take the minimal dist with the center as radius
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([cardDirRa, cardDirDec]) => {
ra = this.centerRaDec[0] + cardDirRa * this.radiusDegrees;
dec = this.centerRaDec[1] + cardDirDec * this.radiusDegrees;
vertOnCircle = AladinUtils.radecToViewXy(ra, dec, view);
if (vertOnCircle) {
dx = vertOnCircle[0] - this.center.x;
dy = vertOnCircle[1] - this.center.y;
this.radius = Math.min(Math.sqrt(dx*dx + dy*dy), this.radius);
hidden = false;
}
});
} else {
ra = this.centerRaDec[0] + this.radiusDegrees;
dec = this.centerRaDec[1];
vertOnCircle = AladinUtils.radecToViewXy(ra, dec, view);
if (vertOnCircle) {
dx = vertOnCircle[0] - this.center.x;
dy = vertOnCircle[1] - this.center.y;
this.radius = Math.sqrt(dx*dx + dy*dy);
hidden = false;
}
}
// Second check, the radius is not too big in the clipping space
let [x1c, y1c] = AladinUtils.viewXyToClipXy(this.center.x, this.center.y, view);
let [x2c, y2c] = AladinUtils.viewXyToClipXy(circlePtXyView[0], circlePtXyView[1], view);
let mag2 = (x1c - x2c)*(x1c - x2c) + (y1c - y2c)*(y1c - y2c);
if (mag2 > 0.2) {
if (hidden) {
return;
}
// Then we can draw
var dx = circlePtXyView[0] - this.center.x;
var dy = circlePtXyView[1] - this.center.y;
this.radius = Math.sqrt(dx*dx + dy*dy);
var baseColor = this.color;
if (! baseColor && this.overlay) {

1
src/js/Constants.ts Normal file
View File

@@ -0,0 +1 @@
export const JSONP_PROXY = "https://alaskybis.cds.unistra.fr/cgi/JSONProxy";

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
import { Overlay } from "./Overlay.js";

View File

@@ -34,7 +34,7 @@
*****************************************************************************/
import { AladinUtils } from './AladinUtils.js';
import { Utils } from './Utils.js';
import { Utils } from './Utils';
export let Footprint= (function() {
// constructor
@@ -46,13 +46,15 @@ export let Footprint= (function() {
this.shapes = shapes;
this.isShowing = true;
this.overlay = null;
};
Footprint.prototype.setCatalog = function(catalog) {
if (this.source) {
this.source.setCatalog(catalog);
}
};
};
Footprint.prototype.show = function() {
if (this.isShowing) {
@@ -91,7 +93,7 @@ export let Footprint= (function() {
Footprint.prototype.setSelectionColor = function(color) {
this.shapes.forEach((shape) => shape.setSelectionColor(color))
};
Footprint.prototype.isFootprint = function() {
return true;
}
@@ -125,6 +127,10 @@ export let Footprint= (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) {
let s = this.source;

View File

@@ -7,10 +7,11 @@
import { SimbadPointer } from "./SimbadPointer.js";
import { PlanetaryFeaturesPointer } from "./PlanetaryFeaturesPointer.js";
import { Utils } from './Utils';
// allow to call either Simbad or Planetary features Pointers
export let GenericPointer = (function (view, e) {
const xymouse = view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(view.imageCanvas, e);
let radec = view.wasm.screenToWorld(xymouse.x, xymouse.y);
if (radec) {
// sky case
@@ -29,4 +30,4 @@ export let GenericPointer = (function (view, e) {
console.log("The location you clicked on is out of the view.");
}
}
)
)

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import $ from 'jquery';
export let HiPSDefinition = (function() {

View File

@@ -27,7 +27,7 @@
* Authors: Thomas Boch & Matthieu Baumann [CDS]
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { HiPSDefinition } from "./HiPSDefinition.js";
import { MocServer } from "./MocServer.js";
@@ -202,4 +202,4 @@ HiPSProperties.getFasterMirrorUrl = function (metadata) {
}
})
.then((url) => Utils.fixURLForHTTPS(url));
}
}

View File

@@ -30,7 +30,7 @@
import { ALEvent } from "./events/ALEvent.js";
import { ColorCfg } from "./ColorCfg.js";
import { ImageLayer } from "./ImageLayer.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
export let ImageFITS = (function () {

View File

@@ -27,7 +27,7 @@
* Authors: Thomas Boch & Matthieu Baumann [CDS]
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { ALEvent } from "./events/ALEvent.js";
import { CooFrameEnum } from "./CooFrameEnum.js"
import { ColorCfg } from "./ColorCfg.js";

View File

@@ -10,8 +10,9 @@
*****************************************************************************/
import { Aladin } from "./Aladin.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { Color } from "./Color.js";
import { ALEvent } from "./events/ALEvent.js";
export let MOC = (function() {

View File

@@ -17,7 +17,7 @@
// along with Aladin Lite.
//
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
/******************************************************************************

View File

@@ -30,7 +30,7 @@
*
*****************************************************************************/
import { Utils } from './Utils.js';
import { Utils } from './Utils';
import A from "./A.js";

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import $ from 'jquery';

View File

@@ -30,7 +30,7 @@
* Author: Thomas Boch [CDS]
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
export let PlanetaryFeaturesPointer = (function() {

View File

@@ -35,17 +35,54 @@
import { AladinUtils } from './AladinUtils.js';
import { Line } from './Line.js';
import { Utils } from './Utils.js';
import { Utils } from './Utils';
import { Overlay } from "./Overlay.js";
import { ProjectionEnum, projectionNames } from "./ProjectionEnum.js";
export let Polyline= (function() {
function _calculateMag2ForNoSinProjections(line, view) {
// check if the line is too big (in the clip space) to be drawn
const [x1, y1] = AladinUtils.viewXyToClipXy(line.x1, line.y1, view);
const [x2, y2] = AladinUtils.viewXyToClipXy(line.x2, line.y2, view);
const mag2 = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2);
return mag2;
}
function _isAcrossCollignonZoneForHpxProjection(line, view) {
const [x1, y1] = AladinUtils.viewXyToClipXy(line.x1, line.y1, view);
const [x2, y2] = AladinUtils.viewXyToClipXy(line.x2, line.y2, view);
// x, y, between -1 and 1
let triIdxCollignionZone = function(x, y) {
let xZone = Math.floor((x * 0.5 + 0.5) * 4.0);
return xZone + 4 * (y > 0.0);
};
let isInCollignionZone = function(x, y) {
return Math.abs(y) > 0.5;
};
if (isInCollignionZone(x1, y1) && isInCollignionZone(x2, y2)) {
if (triIdxCollignionZone(x1, y1) === triIdxCollignionZone(x2, y2)) {
return false;
} else {
return true;
}
}
return false;
}
// constructor
let Polyline = function(radecArray, options) {
options = options || {};
this.color = options['color'] || undefined;
this.lineWidth = options["lineWidth"] || 2;
this.fill = options['fill'] || false;
this.fillColor = options['fillColor'] || undefined;
this.opacity = options['opacity'] || undefined;
this.lineWidth = options["lineWidth"] || undefined;
if (options["closed"]) {
this.closed = options["closed"];
@@ -138,7 +175,7 @@ export let Polyline= (function() {
this.overlay.reportChange();
}
};
Polyline.prototype.isFootprint = function() {
// The polyline is a footprint if it describes a polygon (i.e. a closed polyline)
return this.closed;
@@ -163,6 +200,10 @@ export let Polyline= (function() {
baseColor = '#ff0000';
}
if (!this.lineWidth) {
this.lineWidth = this.overlay.lineWidth || 2;
}
if (this.isSelected) {
if(this.selectionColor) {
ctx.strokeStyle = this.selectionColor;
@@ -204,69 +245,116 @@ export let Polyline= (function() {
}
let drawLine;
let fillPoly;
if (view.projection === ProjectionEnum.SIN) {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
if (line.isInsideView(view.width, view.height)) {
line.draw(ctx);
}
};
} else {
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
if (index === 0) {
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
} else {
ctx.lineTo(line.x1, line.y1);
}
return true;
};
}
} else if (view.projection === ProjectionEnum.HPX) {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
if (_isAcrossCollignonZoneForHpxProjection(line, view)) {
return;
}
if (line.isInsideView(view.width, view.height)) {
// check if the line is too big (in the clip space) to be drawn
const [x1, y1] = AladinUtils.viewXyToClipXy(line.x1, line.y1, view);
const [x2, y2] = AladinUtils.viewXyToClipXy(line.x2, line.y2, view);
const mag2 = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2);
const mag2 = _calculateMag2ForNoSinProjections(line, view);
if (mag2 < 0.1) {
line.draw(ctx);
}
}
};
}
// 3. Check whether the polygon do not cross the view
let nSegment = this.closed ? len : len - 1;
/*
let v0 = this.closed ? len - 1 : 0;
let v1 = this.closed ? 0 : 1;
let v2 = this.closed ? 1 : 2;
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
let drawPolygon = true;
for (var k = 0; k < nSegment; k++) {
let ccwTriOrder = ccwOrder(xyView[v0], xyView[v1], xyView[v2])
if (_isAcrossCollignonZoneForHpxProjection(line, view)) {
return;
}
if (ccwGoodOrder != ccwTriOrder) {
// if it cross the view, we end up here
drawPolygon = false;
const mag2 = _calculateMag2ForNoSinProjections(line, view);
return;
if (mag2 < 0.1) {
if (index === 0) {
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
} else {
ctx.lineTo(line.x1, line.y1);
}
return true;
} else {
return false;
}
};
}
} else {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
v0 = v1;
v1 = v2;
v2 = (v2 + 1) % len;
if (line.isInsideView(view.width, view.height)) {
const mag2 = _calculateMag2ForNoSinProjections(line, view);
if (mag2 < 0.1) {
line.draw(ctx);
}
}
};
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
const mag2 = _calculateMag2ForNoSinProjections(line, view);
if (mag2 < 0.1) {
if (index === 0) {
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
} else {
ctx.lineTo(line.x1, line.y1);
}
return true;
} else {
return false;
}
};
}
}
if (!drawPolygon) {
return;
}*/
// 4. Finally, draw all the polygon, segment by segment
let nSegment = this.closed ? len : len - 1;
let v0 = this.closed ? len - 1 : 0;
let v1 = this.closed ? 0 : 1;
ctx.lineWidth = this.lineWidth;
ctx.beginPath();
for (var k = 0; k < nSegment; k++) {
drawLine(xyView[v0], xyView[v1])
drawLine(xyView[v0], xyView[v1]);
v0 = v1;
v1 = v1 + 1;
@@ -275,6 +363,28 @@ export let Polyline= (function() {
if (!noStroke) {
ctx.stroke();
}
if (this.fill && this.closed) {
v0 = len - 1;
v1 = 0;
let index = 0;
for (var k = 0; k < nSegment; k++) {
if (fillPoly(xyView[v0], xyView[v1], index)) {
index++;
}
v0 = v1;
v1 = v1 + 1;
}
ctx.globalAlpha = 1;
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.globalAlpha = this.opacity;
ctx.fill();
ctx.restore();
}
};
Polyline.prototype.isInStroke = function(ctx, view, x, y) {
@@ -303,7 +413,7 @@ export let Polyline= (function() {
if(this.closed) {
const line = new Line(pointXY[lastPointIdx].x, pointXY[lastPointIdx].y, pointXY[0].x, pointXY[0].y); // new segment
line.draw(ctx, true);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
}

View File

@@ -31,7 +31,7 @@ import { Catalog } from "./Catalog.js";
import { Source } from "./Source.js";
import { Color } from "./Color.js";
import { Coo } from "./libs/astro/coo.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { CooFrameEnum } from "./CooFrameEnum.js";
import $ from 'jquery';

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import $ from 'jquery';

View File

@@ -31,7 +31,7 @@
*
*****************************************************************************/
import { Coo } from "./libs/astro/coo.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
export let SimbadPointer = (function() {

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { Coo } from './libs/astro/coo.js';
import { Utils } from './Utils.js';
import { Utils } from './Utils';
export let URLBuilder = (function() {
let URLBuilder = {

View File

@@ -1,428 +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.
//
/******************************************************************************
* Aladin Lite project
*
* File Utils
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Aladin } from "./Aladin.js";
import $ from 'jquery';
export let Utils = {};
// list of URL domains that can be safely switched from HTTP to HTTPS
Utils.HTTPS_WHITELIST = ['alasky.u-strasbg.fr', 'alaskybis.u-strasbg.fr', 'alasky.unistra.fr', 'alaskybis.unistra.fr',
'alasky.cds.unistra.fr', 'alaskybis.cds.unistra.fr', 'hips.astron.nl', 'jvo.nao.ac.jp',
'archive.cefca.es', 'cade.irap.omp.eu', 'skies.esac.esa.int'];
Utils.cssScale = undefined;
// adding relMouseCoords to HTMLCanvasElement prototype (see http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element )
function relMouseCoords(event) {
if (event.offsetX) {
return {x: event.offsetX, y: event.offsetY};
}
else {
if (!Utils.cssScale) {
var st = window.getComputedStyle(document.body, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform");
var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/;
var matches = tr.match(matrixRegex);
if (matches) {
Utils.cssScale = parseFloat(matches[1]);
}
else {
Utils.cssScale = 1;
}
}
var e = event;
var canvas = e.target;
// http://www.jacklmoore.com/notes/mouse-position/
var target = e.target || e.srcElement;
var style = target.currentStyle || window.getComputedStyle(target, null);
var borderLeftWidth = parseInt(style['borderLeftWidth'], 10);
var borderTopWidth = parseInt(style['borderTopWidth'], 10);
var rect = target.getBoundingClientRect();
var clientX = e.clientX;
var clientY = e.clientY;
if (e.originalEvent.changedTouches) {
clientX = e.originalEvent.changedTouches[0].clientX;
clientY = e.originalEvent.changedTouches[0].clientY;
}
var offsetX = clientX - borderLeftWidth - rect.left;
var offsetY = clientY - borderTopWidth - rect.top
return {x: parseInt(offsetX/Utils.cssScale), y: parseInt(offsetY/Utils.cssScale)};
}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
//Function.prototype.bind polyfill from
//https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (obj) {
// closest thing possible to the ECMAScript 5 internal IsCallable function
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () { },
bound = function () {
return self.apply(this instanceof nop ? this : (obj || {}),
args.concat(slice.call(arguments)));
};
bound.prototype = this.prototype;
return bound;
};
}
//$ = $ || jQuery;
/* source : http://stackoverflow.com/a/8764051 */
$.urlParam = function(name, queryString){
if (queryString===undefined) {
queryString = location.search;
}
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(queryString)||[,""])[1].replace(/\+/g, '%20'))||null;
};
/* source: http://stackoverflow.com/a/1830844 */
Utils.isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
Utils.isInt = function(n) {
return Utils.isNumber(n) && Math.floor(n)==n;
};
/* 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;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
};
/* return a throttled function, to rate limit the number of calls (by default, one call every 250 milliseconds) */
Utils.throttle = function(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
/* A LRU cache, inspired by https://gist.github.com/devinus/409353#file-gistfile1-js */
// TODO : utiliser le LRU cache pour les tuiles images
Utils.LRUCache = function (maxsize) {
this._keys = [];
this._items = {};
this._expires = {};
this._size = 0;
this._maxsize = maxsize || 1024;
};
Utils.LRUCache.prototype = {
set: function (key, value) {
var keys = this._keys,
items = this._items,
expires = this._expires,
size = this._size,
maxsize = this._maxsize;
if (size >= maxsize) { // remove oldest element when no more room
keys.sort(function (a, b) {
if (expires[a] > expires[b]) return -1;
if (expires[a] < expires[b]) return 1;
return 0;
});
size--;
}
keys[size] = key;
items[key] = value;
expires[key] = Date.now();
size++;
this._keys = keys;
this._items = items;
this._expires = expires;
this._size = size;
},
get: function (key) {
var item = this._items[key];
if (item) { this._expires[key] = Date.now(); }
return item;
},
keys: function() {
return this._keys;
}
};
////////////////////////////////////////////////////////////////////////////:
/**
Fetch an url with the method GET, given a list of potential mirrors
An optional object can be given with the following keywords accepted:
* data: an object storing the params associated to the URL
* contentType: specify the content type returned from the url (no verification is done, it is not mandatory to put it)
* timeout: A maximum request time. If exceeded, the request is aborted and the next url will be fetched
This method assumes the URL are CORS-compatible, no proxy will be used
A promise is returned. When all the urls fail, a rejected Promise is returned so that it can be catched afterwards
*/
Utils.loadFromMirrors = function(urls, options) {
const contentType = options && options.contentType || "application/json";
const data = options && options.data || undefined;
const timeout = options && options.timeout || 5000;
// Base case, when all urls have been fetched and failed
if (urls.length === 0) {
return Promise.reject("None of the urls given can be fetched!");
}
// A controller that can abort the query when a timeout is reached
const controller = new AbortController();
// Launch a timemout that will interrupt the fetch if it has not yet succeded:
const timeoutId = setTimeout(() => controller.abort(), timeout);
const init = {
// *GET, POST, PUT, DELETE, etc.
method: "GET",
headers: {
"Content-Type": contentType
},
// no-cors, *cors, same-origin
mode: 'cors',
// *default, no-cache, reload, force-cache, only-if-cached
cache: 'default',
// manual, *follow, error
redirect: 'follow',
// Abort the request when a timeout exceeded
signal: controller.signal,
};
const url = urls[0] + '?' + new URLSearchParams(data);
return fetch(url, init)
.then((response) => {
// completed request before timeout fired
clearTimeout(timeoutId)
if (!response.ok) {
return Promise.reject("Url: ", urls[0], " cannot be reached in some way.");
} else {
return response;
}
})
.catch((e) => {
// The request aborted because it was to slow, fetch the next url given recursively
return Utils.loadFromMirrors(urls.slice(1), options);
});
}
// return the jquery ajax object configured with the requested parameters
// by default, we use the proxy (safer, as we don't know if the remote server supports CORS)
Utils.getAjaxObject = function(url, method, dataType, useProxy) {
if (useProxy!==false) {
useProxy = true;
}
if (useProxy===true) {
var urlToRequest = Aladin.JSONP_PROXY + '?url=' + encodeURIComponent(url);
}
else {
urlToRequest = url;
}
method = method || 'GET';
dataType = dataType || null;
return $.ajax({
url: urlToRequest,
method: method,
dataType: dataType
});
};
// return true if script is executed in a HTTPS context
// return false otherwise
Utils.isFileContext = function() {
return ( window.location.protocol === 'file:' );
};
Utils.isHttpsContext = function() {
return ( window.location.protocol === 'https:' );
};
Utils.isHttpContext = function() {
return ( window.location.protocol === 'http:' );
};
Utils.fixURLForHTTPS = function(url) {
const switchToHttps = Utils.HTTPS_WHITELIST.some(element => {
return url.includes(element);
});
if (switchToHttps) {
return url.replace('http://', 'https://');
}
return url;
};
// generate an absolute URL from a relative URL
// example: getAbsoluteURL('foo/bar/toto') return http://cds.unistra.fr/AL/foo/bar/toto if executed from page http://cds.unistra.fr/AL/
Utils.getAbsoluteURL = function(url) {
var a = document.createElement('a');
a.href = url;
return a.href;
};
// generate a valid v4 UUID
Utils.uuidv4 = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* @function
* @description Deep clone a class instance.
* @param {object} instance The class instance you want to clone.
* @returns {object} A new cloned instance.
*/
Utils.clone = function(instance) {
return Object.assign(
Object.create(
// Set the prototype of the new object to the prototype of the instance.
// Used to allow new object behave like class instance.
Object.getPrototypeOf(instance),
),
// Prevent shallow copies of nested structures like arrays, etc
JSON.parse(JSON.stringify(instance)),
);
}
Utils.getDroppedFilesHandler = function(ev) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
let items;
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
items = [...ev.dataTransfer.items];
} else {
// Use DataTransfer interface to access the file(s)
items = [...ev.dataTransfer.files];
}
const files = items.filter((item) => {
// If dropped items aren't files, reject them
return item.kind === 'file';
})
.map((item) => item.getAsFile());
return files;
}
Utils.dragOverHandler = function(ev) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
}
Utils.requestCORSIfNotSameOrigin = function(url) {
return (new URL(url, window.location.href)).origin !== window.location.origin;
}
// Check the protocol, for http ones, use a CORS compatible proxy
Utils.handleCORSNotSameOrigin = function(url) {
if (Utils.requestCORSIfNotSameOrigin(url)) {
// http(s) protocols and not in localhost
let proxiedUrl = new URL(Aladin.JSONP_PROXY);
proxiedUrl.searchParams.append("url", url);
url = proxiedUrl;
}
return url;
}
Utils.deepCopy = function(orig) {
return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig);
}
Utils.download = function(url, name = undefined) {
const a = document.createElement('a')
a.href = url
if (name) {
a.download = name;
}
a.click()
}

413
src/js/Utils.ts Normal file
View File

@@ -0,0 +1,413 @@
// 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 Utils
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import {JSONP_PROXY} from "@/js/Constants";
export let Utils: any = {}
// list of URL domains that can be safely switched from HTTP to HTTPS
Utils.HTTPS_WHITELIST = ['alasky.u-strasbg.fr', 'alaskybis.u-strasbg.fr', 'alasky.unistra.fr', 'alaskybis.unistra.fr',
'alasky.cds.unistra.fr', 'alaskybis.cds.unistra.fr', 'hips.astron.nl', 'jvo.nao.ac.jp',
'archive.cefca.es', 'cade.irap.omp.eu', 'skies.esac.esa.int']
Utils.cssScale = undefined
Utils.relMouseCoords = function (canvas: HTMLCanvasElement, event: MouseEvent) {
if (event.offsetX) {
return {x: event.offsetX, y: event.offsetY}
} else {
if (!Utils.cssScale) {
var st = window.getComputedStyle(document.body, null)
var tr = st.getPropertyValue('-webkit-transform') ||
st.getPropertyValue('-moz-transform') ||
st.getPropertyValue('-ms-transform') ||
st.getPropertyValue('-o-transform') ||
st.getPropertyValue('transform')
var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/
var matches = tr.match(matrixRegex)
if (matches) {
Utils.cssScale = parseFloat(matches[1])
} else {
Utils.cssScale = 1
}
}
var e = event
// http://www.jacklmoore.com/notes/mouse-position/
var target = e.target || e.srcElement
var style = target.currentStyle || window.getComputedStyle(target, null)
var borderLeftWidth = parseInt(style['borderLeftWidth'], 10)
var borderTopWidth = parseInt(style['borderTopWidth'], 10)
var rect = target.getBoundingClientRect()
var clientX = e.clientX
var clientY = e.clientY
if (e.originalEvent.changedTouches) {
clientX = e.originalEvent.changedTouches[0].clientX
clientY = e.originalEvent.changedTouches[0].clientY
}
var offsetX = clientX - borderLeftWidth - rect.left
var offsetY = clientY - borderTopWidth - rect.top
return {x: Math.round(offsetX / Utils.cssScale), y: Math.round(offsetY / Utils.cssScale)}
}
}
//Function.prototype.bind polyfill from
//https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (obj) {
// closest thing possible to the ECMAScript 5 internal IsCallable function
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
}
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () {
},
bound = function () {
return self.apply(this instanceof nop ? this : (obj || {}),
args.concat(slice.call(arguments)))
}
bound.prototype = this.prototype
return bound
}
}
/* source : http://stackoverflow.com/a/8764051 */
Utils.urlParam = function (name: string, queryString: string | undefined) {
if (queryString === undefined) {
queryString = location.search
}
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(queryString) || [, ''])[1].replace(/\+/g, '%20')) || null
}
/* source: http://stackoverflow.com/a/1830844 */
Utils.isNumber = function (n: string | number) {
return !isNaN(parseFloat(n as string)) && isFinite(n as number)
}
Utils.isInt = function (n: string | number) {
return Utils.isNumber(n) && Math.floor(n as number) === n
}
/* 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
return function () {
var context = this, args = arguments
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
/* return a throttled function, to rate limit the number of calls (by default, one call every 250 milliseconds) */
Utils.throttle = function (fn, threshhold, scope) {
threshhold || (threshhold = 250)
var last,
deferTimer
return function () {
var context = scope || this
var now = +new Date,
args = arguments
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fn.apply(context, args)
}, threshhold)
} else {
last = now
fn.apply(context, args)
}
}
}
/* A LRU cache, inspired by https://gist.github.com/devinus/409353#file-gistfile1-js */
// TODO : utiliser le LRU cache pour les tuiles images
Utils.LRUCache = function (maxsize) {
this._keys = []
this._items = {}
this._expires = {}
this._size = 0
this._maxsize = maxsize || 1024
}
Utils.LRUCache.prototype = {
set: function (key, value) {
var keys = this._keys,
items = this._items,
expires = this._expires,
size = this._size,
maxsize = this._maxsize
if (size >= maxsize) { // remove oldest element when no more room
keys.sort(function (a, b) {
if (expires[a] > expires[b]) return -1
if (expires[a] < expires[b]) return 1
return 0
})
size--
}
keys[size] = key
items[key] = value
expires[key] = Date.now()
size++
this._keys = keys
this._items = items
this._expires = expires
this._size = size
},
get: function (key) {
var item = this._items[key]
if (item) {
this._expires[key] = Date.now()
}
return item
},
keys: function () {
return this._keys
}
}
////////////////////////////////////////////////////////////////////////////:
/**
Fetch an url with the method GET, given a list of potential mirrors
An optional object can be given with the following keywords accepted:
* data: an object storing the params associated to the URL
* contentType: specify the content type returned from the url (no verification is done, it is not mandatory to put it)
* timeout: A maximum request time. If exceeded, the request is aborted and the next url will be fetched
This method assumes the URL are CORS-compatible, no proxy will be used
A promise is returned. When all the urls fail, a rejected Promise is returned so that it can be catched afterwards
*/
Utils.loadFromMirrors = function (urls, options) {
const contentType = options && options.contentType || 'application/json'
const data = options && options.data || undefined
const timeout = options && options.timeout || 5000
// Base case, when all urls have been fetched and failed
if (urls.length === 0) {
return Promise.reject('None of the urls given can be fetched!')
}
// A controller that can abort the query when a timeout is reached
const controller = new AbortController()
// Launch a timemout that will interrupt the fetch if it has not yet succeded:
const timeoutId = setTimeout(() => controller.abort(), timeout)
const init = {
// *GET, POST, PUT, DELETE, etc.
method: 'GET',
headers: {
'Content-Type': contentType
},
// no-cors, *cors, same-origin
mode: 'cors',
// *default, no-cache, reload, force-cache, only-if-cached
cache: 'default',
// manual, *follow, error
redirect: 'follow',
// Abort the request when a timeout exceeded
signal: controller.signal,
}
const url = urls[0] + '?' + new URLSearchParams(data)
return fetch(url, init)
.then((response) => {
// completed request before timeout fired
clearTimeout(timeoutId)
if (!response.ok) {
return Promise.reject('Url: ', urls[0], ' cannot be reached in some way.')
} else {
return response
}
})
.catch((e) => {
// The request aborted because it was to slow, fetch the next url given recursively
return Utils.loadFromMirrors(urls.slice(1), options)
})
}
// return the jquery ajax object configured with the requested parameters
// by default, we use the proxy (safer, as we don't know if the remote server supports CORS)
Utils.getAjaxObject = function (url, method, dataType, useProxy) {
if (useProxy !== false) {
useProxy = true
}
if (useProxy === true) {
var urlToRequest = JSONP_PROXY + '?url=' + encodeURIComponent(url)
} else {
urlToRequest = url
}
method = method || 'GET'
dataType = dataType || null
return $.ajax({
url: urlToRequest,
method: method,
dataType: dataType
})
}
// return true if script is executed in a HTTPS context
// return false otherwise
Utils.isFileContext = function () {
return (window.location.protocol === 'file:')
}
Utils.isHttpsContext = function () {
return (window.location.protocol === 'https:')
}
Utils.isHttpContext = function () {
return (window.location.protocol === 'http:')
}
Utils.fixURLForHTTPS = function (url) {
const switchToHttps = Utils.HTTPS_WHITELIST.some(element => {
return url.includes(element)
})
if (switchToHttps) {
return url.replace('http://', 'https://')
}
return url
}
// generate an absolute URL from a relative URL
// example: getAbsoluteURL('foo/bar/toto') return http://cds.unistra.fr/AL/foo/bar/toto if executed from page http://cds.unistra.fr/AL/
Utils.getAbsoluteURL = function (url) {
var a = document.createElement('a')
a.href = url
return a.href
}
// generate a valid v4 UUID
Utils.uuidv4 = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
/**
* @function
* @description Deep clone a class instance.
* @param {object} instance The class instance you want to clone.
* @returns {object} A new cloned instance.
*/
Utils.clone = function (instance) {
return Object.assign(
Object.create(
// Set the prototype of the new object to the prototype of the instance.
// Used to allow new object behave like class instance.
Object.getPrototypeOf(instance),
),
// Prevent shallow copies of nested structures like arrays, etc
JSON.parse(JSON.stringify(instance)),
)
}
Utils.getDroppedFilesHandler = function (ev) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault()
let items
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
items = [...ev.dataTransfer.items]
} else {
// Use DataTransfer interface to access the file(s)
items = [...ev.dataTransfer.files]
}
const files = items.filter((item) => {
// If dropped items aren't files, reject them
return item.kind === 'file'
})
.map((item) => item.getAsFile())
return files
}
Utils.dragOverHandler = function (ev) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault()
}
Utils.requestCORSIfNotSameOrigin = function (url) {
return (new URL(url, window.location.href)).origin !== window.location.origin
}
// Check the protocol, for http ones, use a CORS compatible proxy
Utils.handleCORSNotSameOrigin = function (url) {
if (Utils.requestCORSIfNotSameOrigin(url)) {
// http(s) protocols and not in localhost
let proxiedUrl = new URL(JSONP_PROXY)
proxiedUrl.searchParams.append('url', url)
url = proxiedUrl
}
return url
}
Utils.deepCopy = function (orig) {
return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
}
Utils.download = function(url, name = undefined) {
const a = document.createElement('a')
a.href = url
if (name) {
a.download = name;
}
a.click()
}

View File

@@ -34,7 +34,7 @@ import A from "./A.js";
import { Popup } from "./Popup.js";
import { HealpixGrid } from "./HealpixGrid.js";
import { ProjectionEnum } from "./ProjectionEnum.js";
import { Utils } from "./Utils.js";
import { Utils } from "./Utils";
import { GenericPointer } from "./GenericPointer.js";
import { Stats } from "./libs/Stats.js";
import { Circle } from "./Circle.js";
@@ -256,7 +256,7 @@ export let View = (function () {
if (fov !== this.oldFov) {
const fovChangedFn = this.aladin.callbacksByEventName['zoomChanged'];
(typeof fovChangedFn === 'function') && fovChangedFn(fov);
// finally, save fov value
this.oldFov = fov;
}
@@ -348,9 +348,9 @@ export let View = (function () {
this.catalogCtx.canvas.width = this.width;
this.catalogCtx.canvas.height = this.height;
/*this.gridCtx = this.gridCanvas.getContext("2d");
this.gridCtx = this.gridCanvas.getContext("2d");
this.gridCtx.canvas.width = this.width;
this.gridCtx.canvas.height = this.height;*/
this.gridCtx.canvas.height = this.height;
pixelateCanvasContext(this.imageCtx, this.aladin.options.pixelateCanvas);
@@ -461,7 +461,7 @@ export let View = (function () {
// various listeners
let onDblClick = function (e) {
var xymouse = view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(view.imageCanvas, e);
// deselect all the selected sources with Select panel
view.deselectObjects()
@@ -485,7 +485,7 @@ export let View = (function () {
// do something here...
e.preventDefault();
}, false);
let cutMinInit = null
let cutMaxInit = null;
@@ -494,7 +494,7 @@ export let View = (function () {
e.preventDefault();
e.stopPropagation();
var xymouse = view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(view.imageCanvas, e);
if (e.which === 3 || e.button === 2) {
view.rightClick = true;
@@ -618,7 +618,7 @@ export let View = (function () {
let tables = selectedObjects.map((objList) => {
// Get the catalog containing that list of objects
let catalog = objList[0].getCatalog();
let rows = objList.map((o) => {
if (o instanceof Footprint) {
return o.source;
@@ -660,7 +660,7 @@ export let View = (function () {
view.mustClearCatalog = true;
view.dragx = view.dragy = null;
const xymouse = view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(view.imageCanvas, e);
if (e.type === "mouseout" || e.type === "touchend" || e.type === "touchcancel") {
view.updateLocation(xymouse.x, xymouse.y, true);
@@ -708,13 +708,13 @@ export let View = (function () {
}
var objClickedFunction = view.aladin.callbacksByEventName['objectClicked'];
(typeof objClickedFunction === 'function') && objClickedFunction(o);
(typeof objClickedFunction === 'function') && objClickedFunction(o, xymouse);
if (o.isFootprint()) {
var footprintClickedFunction = view.aladin.callbacksByEventName['footprintClicked'];
if (typeof footprintClickedFunction === 'function' && o != view.lastClickedObject) {
var ret = footprintClickedFunction(o);
var ret = footprintClickedFunction(o, xymouse);
}
}
@@ -728,7 +728,7 @@ export let View = (function () {
if (view.lastClickedObject) {
view.aladin.measurementTable.hide();
view.popup.hide();
// Deselect the last clicked object
if (view.lastClickedObject instanceof Ellipse || view.lastClickedObject instanceof Circle || view.lastClickedObject instanceof Polyline) {
view.lastClickedObject.deselect();
@@ -736,9 +736,9 @@ export let View = (function () {
// Case where lastClickedObject is a Source
view.lastClickedObject.actionOtherObjectClicked();
}
var objClickedFunction = view.aladin.callbacksByEventName['objectClicked'];
(typeof objClickedFunction === 'function') && objClickedFunction(null);
(typeof objClickedFunction === 'function') && objClickedFunction(null, xymouse);
view.lastClickedObject = null;
}
@@ -766,11 +766,11 @@ export let View = (function () {
var lastMouseMovePos = null;
$(view.catalogCanvas).bind("mousemove touchmove", function (e) {
e.preventDefault();
var xymouse = view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(view.imageCanvas, e);
if (view.rightClick) {
var onRightClickMoveFunction = view.aladin.callbacksByEventName['rightClickMove'];
if (typeof onRightClickMoveFunction === 'function') {
if (typeof onRightClickMoveFunction === 'function') {
onRightClickMoveFunction(xymouse.x, xymouse.y);
// do not process further
@@ -786,12 +786,12 @@ export let View = (function () {
};
const cx = (xymouse.x - cs.x) / view.catalogCanvas.clientWidth;
const cy = -(xymouse.y - cs.y) / view.catalogCanvas.clientHeight;
const offset = (cutMaxInit - cutMinInit) * cx;
const lr = offset + (1.0 - 2.0 * cy) * cutMinInit;
const rr = offset + (1.0 + 2.0 * cy) * cutMaxInit;
if (lr <= rr) {
selectedLayer.setCuts(lr, rr)
}
@@ -867,12 +867,12 @@ export let View = (function () {
view.setCursor('pointer');
if (typeof objHoveredFunction === 'function' && o != lastHoveredObject) {
var ret = objHoveredFunction(o);
var ret = objHoveredFunction(o, xymouse);
}
if (o.isFootprint()) {
if (typeof footprintHoveredFunction === 'function' && o != lastHoveredObject) {
var ret = footprintHoveredFunction(o);
var ret = footprintHoveredFunction(o, xymouse);
}
}
@@ -885,10 +885,10 @@ export let View = (function () {
if (lastHoveredObject.isFootprint()) {
view.requestRedraw();
}
if (typeof objHoveredStopFunction === 'function') {
// call callback function to notify we left the hovered object
var ret = objHoveredStopFunction(lastHoveredObject);
var ret = objHoveredStopFunction(lastHoveredObject, xymouse);
}
}
@@ -1959,7 +1959,7 @@ export let View = (function () {
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 || cat.readOnly) {
continue;
}
@@ -1984,7 +1984,7 @@ export let View = (function () {
}
let closest = null;
footprints.forEach((footprint) => {
// Hidden footprints are not considered
if (footprint.isShowing && footprint.isInStroke(ctx, this, x, y)) {
@@ -2022,7 +2022,7 @@ export let View = (function () {
if (this.catalogs) {
for (var k = 0; k < this.catalogs.length; k++) {
let catalog = this.catalogs[k];
let closest = this.closestFootprints(catalog.footprints, ctx, x, y);
if (closest) {
ctx.lineWidth = pastLineWidth;

View File

@@ -18,7 +18,7 @@
//
import { MocServer } from "../MocServer.js";
import { Utils } from "../Utils.js";
import { Utils } from "../Utils";
import autocomplete from 'autocompleter';
import $ from 'jquery';

View File

@@ -30,8 +30,9 @@
*
*****************************************************************************/
import { Coo } from "../libs/astro/coo.js";
import { CooFrameEnum } from "../CooFrameEnum.js";
import { Coo } from '../libs/astro/coo.js';
import { CooFrameEnum } from '../CooFrameEnum.js';
import { Utils } from '../Utils';
export class ContextMenu {
@@ -42,11 +43,11 @@ export class ContextMenu {
_hideMenu(e) {
//if (e === true || !this.contextMenuUl.contains(e.target)) {
this.contextMenuUl.remove();
document.removeEventListener('click', this._hideMenu);
window.removeEventListener('resize', this._hideOnResize);
this.contextMenuUl.remove();
document.removeEventListener('click', this._hideMenu);
window.removeEventListener('resize', this._hideOnResize);
this.isShowing = false;
this.isShowing = false;
//}
}
@@ -57,31 +58,27 @@ export class ContextMenu {
_attachOption(target, opt, xymouse) {
const item = document.createElement('li');
item.className = 'aladin-context-menu-item';
if (opt.label=='Copy position') {
if (opt.label == 'Copy position') {
try {
const pos = this.aladin.pix2world(xymouse.x, xymouse.y);
const coo = new Coo(pos[0], pos[1], 6);
let posStr;
if (this.aladin.view.cooFrame == CooFrameEnum.J2000) {
posStr = coo.format('s/');
}
else if (this.aladin.view.cooFrame == CooFrameEnum.J2000d) {
} else if (this.aladin.view.cooFrame == CooFrameEnum.J2000d) {
posStr = coo.format('d/');
}
else {
} else {
posStr = coo.format('d/');
}
item.innerHTML = '<span>' + posStr + '</span>';
}
catch(e) {
} catch (e) {
item.innerHTML = '<span></span>';
}
}
else {
} else {
item.innerHTML = '<span>' + opt.label + '</span>';
}
if (opt.subMenu && opt.subMenu.length>0) {
if (opt.subMenu && opt.subMenu.length > 0) {
item.innerHTML += '<span style="position: absolute; right: 4px;">▶</span>';
}
@@ -89,10 +86,9 @@ export class ContextMenu {
item.addEventListener('click', e => {
e.stopPropagation();
if (!opt.subMenu || opt.subMenu.length === 0) {
if (opt.label=='Copy position') {
if (opt.label == 'Copy position') {
opt.action(e);
}
else {
} else {
opt.action(this.event);
}
self._hideMenu(true);
@@ -105,19 +101,17 @@ export class ContextMenu {
const subMenu = document.createElement('ul');
subMenu.className = 'aladin-context-sub-menu';
item.appendChild(subMenu);
opt.subMenu.forEach(subOpt => this._attachOption(subMenu, subOpt))
opt.subMenu.forEach(subOpt => this._attachOption(subMenu, subOpt));
}
}
_showMenu(e) {
this.contextMenuUl.className = 'aladin-context-menu';
this.contextMenuUl.innerHTML = '';
const xymouse = this.aladin.view.imageCanvas.relMouseCoords(e);
const xymouse = Utils.relMouseCoords(this.aladin.view.imageCanvas, e);
this.menuOptions.forEach(opt => this._attachOption(this.contextMenuUl, opt, xymouse))
this.menuOptions.forEach(opt => this._attachOption(this.contextMenuUl, opt, xymouse));
document.body.appendChild(this.contextMenuUl);
const { innerWidth, innerHeight } = window;
@@ -154,7 +148,6 @@ export class ContextMenu {
}
attachTo(el, options) {
this.contextMenuUl = document.createElement('ul');
this.menuOptions = options;

View File

@@ -65,7 +65,7 @@
// Coordinates grid plot
let labelCoordinatesGridCb = $('<div class="aladin-label">Coo grid options</div>');
let cooGridOptions = $('<div class="layer-options"><table><tbody><tr><td>Color</td><td><input type="color" value="#00ff00"></td></tr><tr><td>Opacity</td><td><input class="aladin-input opacity" value="0.5" type="range" min="0.0" max="1.0" step="0.05"></td></tr><tr><td>Thickness</td><td><input class="aladin-input thickness" value="3.0" type="range" min="0.5" max="10.0" step="0.01"></td></tr><tr><td>Label size</td><td><input class="aladin-input label-size" type="range" value="30" min="10" max="60" step="0.01"></td></tr></table></div>');
let cooGridOptions = $('<div class="layer-options"><table><tbody><tr><td>Color</td><td><input type="color" value="#00ff00"></td></tr><tr><td>Opacity</td><td><input class="aladin-input opacity" value="0.5" type="range" min="0.0" max="1.0" step="0.05"></td></tr><tr><td>Thickness</td><td><input class="aladin-input thickness" value="3.0" type="range" min="0.5" max="10.0" step="0.01"></td></tr><tr><td>Label size</td><td><input class="aladin-input label-size" type="range" value="15" min="5" max="30" step="0.01"></td></tr></table></div>');
layerBox.append(labelCoordinatesGridCb).append(cooGridOptions);
let gridColorInput = cooGridOptions.find('input[type="color"]');

View File

@@ -19,7 +19,7 @@
import A from "../A.js";
import { MocServer } from "../MocServer.js";
import { Utils } from "../Utils.js";
import { Utils } from "../Utils";
import autocomplete from 'autocompleter';
import $ from 'jquery';

View File

@@ -30,7 +30,7 @@
*****************************************************************************/
import { VOTable } from "./VOTable.js";
import { Utils } from './../Utils.js';
import { Utils } from './../Utils';
export let Datalink = (function() {

View File

@@ -30,7 +30,7 @@
*****************************************************************************/
import { VOTable } from "./VOTable.js";
import { Datalink } from "./Datalink.js";
import { Utils } from '../Utils.js';
import { Utils } from '../Utils';
export let ObsCore = (function() {
@@ -241,4 +241,3 @@
return ObsCore;
})();

View File

@@ -28,7 +28,7 @@
import { ALEvent } from "../events/ALEvent.js";
import { Catalog } from "../Catalog.js";
import { ObsCore } from "./ObsCore.js";
import { Utils } from "./../Utils.js";
import { Utils } from "./../Utils";
export let VOTable = (function() {

13
tests/unit/Utils.spec.js Normal file
View File

@@ -0,0 +1,13 @@
import {Utils} from '@/js/Utils.ts';
describe('Utils.ts', () => {
beforeEach(() => {
delete window.location;
window.location = {href: {}, search: ''};
});
it('correctly parse a location parameter', () => {
window.location.search = '?survey=DSS';
expect(Utils.urlParam('survey')).toEqual('DSS');
});
});

45
tsconfig.json Normal file
View File

@@ -0,0 +1,45 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"isolatedModules": true,
"noEmit": true,
"lib": [
"esnext",
"dom"
],
"types": [
"node",
"vite/client",
"vitest/globals",
"vitest/importMeta",
],
"typeRoots": [
"node_modules/@types",
"src/types"
],
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
],
"#/*": [
"./tests/unit/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"tests/unit/**/*.ts"
]
}

View File

@@ -1,16 +1,14 @@
import { resolve } from 'path'
import { defineConfig } from 'vite';
// plugins
/// <reference types="vitest" />
import * as path from 'path'
import {resolve} from 'path'
import {defineConfig} from 'vite';
// For wasm inclusion
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
// For wasm genrated by wasm-pack
// For wasm generated by wasm-pack
import wasmPack from 'vite-plugin-wasm-pack';
// To include and minify glsl into the bundle
import glsl from 'vite-plugin-glsl';
// To include css into the bundle
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
@@ -41,7 +39,24 @@ export default defineConfig({
}),
cssInjectedByJsPlugin(),
],
resolve: {
alias: [
{find: '@', replacement: path.resolve(__dirname, '/src')},
{find: '#', replacement: path.resolve(__dirname, '/tests/unit')},
{find: '$', replacement: path.resolve(__dirname, '/tests/e2e')}
],
},
test: {
globals: true,
environment: 'happy-dom',
include: [
'tests/unit/**/*.{test,spec}.{js,ts}'
],
deps: {
inline: ['core/pkg'],
},
},
server: {
open: '/examples/index.html',
},
});
});