// 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 View.js * * Author: Thomas Boch[CDS] * *****************************************************************************/ import { Aladin } from "./Aladin.js"; import { Popup } from "./Popup.js"; import { HealpixGrid } from "./HealpixGrid.js"; import { HpxImageSurvey } from "./HpxImageSurvey.js"; import { ProjectionEnum } from "./ProjectionEnum.js"; import { Projection } from "./libs/astro/projection.js"; import { Coo } from "./libs/astro/coo.js"; import { AladinUtils } from "./AladinUtils.js"; import { HealpixIndex } from "./libs/healpix.js"; import { HealpixCache } from "./HealpixCache.js"; import { SpatialVector } from "./libs/healpix.js"; import { Utils } from "./Utils.js"; import { SimbadPointer } from "./SimbadPointer.js"; import { TileBuffer } from "./TileBuffer.js"; import { Downloader } from "./Downloader.js"; import { Stats } from "./libs/Stats.js"; import { ColorMap } from "./ColorMap.js"; import { Footprint } from "./Footprint.js"; import { Circle } from "./Circle.js"; import { CooFrameEnum } from "./CooFrameEnum.js"; import { CooConversion } from "./CooConversion.js"; import { requestAnimFrame } from "./libs/RequestAnimationFrame.js"; import { loadShaders } from './Shaders.js'; // Import kernel image import kernel from '../core/img/kernel.png'; import colormaps from '../core/img/colormaps/colormaps.png'; import { ImageSurveyLayer } from "./ImageSurveyLayer.js"; export let View = (function() { /** Constructor */ function View (aladin, location, fovDiv, cooFrame, zoom) { this.aladin = aladin; // Add a reference to the WebGL API //this.webglAPI = aladin.webglAPI; this.options = aladin.options; this.aladinDiv = this.aladin.aladinDiv; this.popup = new Popup(this.aladinDiv, this); this.guiCanvas = $('').appendTo(this.aladinDiv)[0]; this.createCanvases(); // Init the WebGL context // At this point, the view has been created so the image canvas too let shaders = loadShaders(); let resources = { 'kernel': kernel, 'colormaps': colormaps, }; try { // Start our Rust application. You can find `WebClient` in `src/lib.rs` this.aladin.webglAPI = new Aladin.wasmLibs.webgl.WebClient(this.aladinDiv.id, shaders, resources); } catch(e) { // For browsers not supporting WebGL2: // 1. Print the original exception message in the console console.log(e) // 2. Add a more explicite message to the end user alert("WebGL2 is not supported by default in your browser. If you're using Safari, you can enable it by checking:\nDeveloper Menu > Experimental Features > WebGL2. You will have to reload the page afterwards.") // TODO: 3. propose a another possibility to the user: a button to run aladin lite v2 instead } this.location = location; this.fovDiv = fovDiv; this.mustClearCatalog = true; this.mustRedrawReticle = true; this.imageSurveysToSet = []; this.mode = View.PAN; this.minFOV = this.maxFOV = null; // by default, no restriction this.fov_limit = 180.0; this.healpixGrid = new HealpixGrid(this.imageCanvas); var lon, lat; lon = lat = 0; this.projectionMethod = ProjectionEnum.SIN; this.projection = new Projection(lon, lat); this.projection.setProjection(this.projectionMethod); //this.zoomLevel = 0; // Prev time of the last frame this.prev = 0; //this.zoomFactor = this.computeZoomFactor(this.zoomLevel); this.zoomFactor = this.aladin.webglAPI.getClipZoomFactor(); this.viewCenter = {lon: lon, lat: lat}; // position of center of view if (cooFrame) { this.cooFrame = cooFrame; } else { this.cooFrame = CooFrameEnum.GAL; } if (cooFrame.system === CooFrameEnum.SYSTEMS.GAL) { console.log() const GAL = Aladin.wasmLibs.webgl.GALCooSys(); this.aladin.webglAPI.setCooSystem(GAL); } else { const ICRSJ2000 = Aladin.wasmLibs.webgl.ICRSJ2000CooSys(); this.aladin.webglAPI.setCooSystem(ICRSJ2000); } if (zoom) { this.setZoom(zoom); } // current reference image survey displayed this.imageSurveys = new Map(); // current catalogs displayed this.catalogs = []; // a dedicated catalog for the popup var c = document.createElement('canvas'); c.width = c.height = 24; var ctx= c.getContext('2d'); ctx.lineWidth = 6.0; ctx.beginPath(); ctx.strokeStyle = '#eee'; ctx.arc(12, 12, 8, 0, 2*Math.PI, true); ctx.stroke(); ctx.lineWidth = 3.0; ctx.beginPath(); ctx.strokeStyle = '#c38'; ctx.arc(12, 12, 8, 0, 2*Math.PI, true); ctx.stroke(); this.catalogForPopup = A.catalog({shape: c, sourceSize: 24}); //this.catalogForPopup = A.catalog({sourceSize: 18, shape: 'circle', color: '#c38'}); this.catalogForPopup.hide(); this.catalogForPopup.setView(this); // overlays (footprints for instance) this.overlays = []; // MOCs this.mocs = []; // reference to all overlay layers (= catalogs + overlays + mocs) this.allOverlayLayers = [] this.tileBuffer = new TileBuffer(); // tile buffer is shared across different image surveys this.fixLayoutDimensions(); this.firstHiPS = true; this.curNorder = 1; this.realNorder = 1; this.curOverlayNorder = 1; // some variables for mouse handling this.dragging = false; this.dragx = null; this.dragy = null; this.needRedraw = true; // zoom pinching this.pinchZoomParameters = { isPinching: false, // true if a pinch zoom is ongoing initialFov: undefined, initialDistance: undefined }; // two-fingers rotation this.fingersRotationParameters = { initialViewAngleFromCenter: undefined, initialFingerAngle: undefined, rotationInitiated: false } this.downloader = new Downloader(this); // the downloader object is shared across all HpxImageSurveys this.flagForceRedraw = false; this.fadingLatestUpdate = null; this.dateRequestRedraw = null; this.showGrid = false; // coordinates grid init(this); // listen to window resize and reshape canvases this.resizeTimer = null; var self = this; $(window).resize(function() { clearTimeout(self.resizeTimer); self.resizeTimer = setTimeout(function() {self.fixLayoutDimensions(self)}, 100); }); // in some contexts (Jupyter notebook for instance), the parent div changes little time after Aladin Lite creation // this results in canvas dimension to be incorrect. // The following line tries to fix this issue setTimeout(function() { var computedWidth = $(self.aladinDiv).width(); var computedHeight = $(self.aladinDiv).height(); if (self.width!==computedWidth || self.height===computedHeight) { self.fixLayoutDimensions(); // As the WebGL backend has been resized correctly by // the previous call, we can get the zoom factor from it self.updateZoomState(); // needed to force recomputation of displayed FoV } }, 1000); }; // different available modes View.PAN = 0; View.SELECT = 1; View.TOOL_SIMBAD_POINTER = 2; // TODO: should be put as an option at layer level View.DRAW_SOURCES_WHILE_DRAGGING = true; View.DRAW_MOCS_WHILE_DRAGGING = true; View.CALLBACKS_THROTTLE_TIME_MS = 100; // minimum time between two consecutive callback calls // (re)create needed canvases View.prototype.createCanvases = function() { var a = $(this.aladinDiv); //a.find('.aladin-webglCanvas').remove(); a.find('.aladin-imageCanvas').remove(); a.find('.aladin-catalogCanvas').remove(); a.find('.aladin-reticleCanvas').remove(); a.find('.aladin-gridCanvas').remove(); //a.find('.aladin-guiCanvas').remove(); // canvas to draw the images //this.webglCanvas = $("").appendTo(this.aladinDiv)[0]; // canvas to draw the overlays // canvas to draw the gui //this.guiCanvas = $("").appendTo(this.aladinDiv)[0]; this.imageCanvas = $("").appendTo(this.aladinDiv)[0]; // canvas to draw the grid this.gridCanvas = $("").appendTo(this.aladinDiv)[0]; // canvas to draw the catalogs this.catalogCanvas = $("").appendTo(this.aladinDiv)[0]; // canvas to draw the reticle this.reticleCanvas = $("").appendTo(this.aladinDiv)[0]; }; // called at startup and when window is resized // The WebGL backend is resized View.prototype.fixLayoutDimensions = function() { Utils.cssScale = undefined; var computedWidth = $(this.aladinDiv).width(); var computedHeight = $(this.aladinDiv).height(); this.width = Math.max(computedWidth, 1); this.height = Math.max(computedHeight, 1); // this prevents many problems when div size is equal to 0 this.cx = this.width/2; this.cy = this.height/2; this.largestDim = Math.max(this.width, this.height); this.smallestDim = Math.min(this.width, this.height); this.ratio = this.largestDim/this.smallestDim; this.mouseMoveIncrement = 160/this.largestDim; // reinitialize 2D context this.imageCtx = this.imageCanvas.getContext("webgl2"); this.aladin.webglAPI.resize(this.width, this.height); this.catalogCtx = this.catalogCanvas.getContext("2d"); this.reticleCtx = this.reticleCanvas.getContext("2d"); this.gridCtx = this.gridCanvas.getContext("2d"); this.imageCtx.canvas.width = this.width; this.catalogCtx.canvas.width = this.width; this.reticleCtx.canvas.width = this.width; this.gridCtx.canvas.width = this.width; this.imageCtx.canvas.height = this.height; this.catalogCtx.canvas.height = this.height; this.reticleCtx.canvas.height = this.height; this.gridCtx.canvas.height = this.height; pixelateCanvasContext(this.imageCtx, this.aladin.options.pixelateCanvas); // change logo if (!this.logoDiv) { this.logoDiv = $(this.aladinDiv).find('.aladin-logo')[0]; } if (this.width>800) { $(this.logoDiv).removeClass('aladin-logo-small'); $(this.logoDiv).addClass('aladin-logo-large'); $(this.logoDiv).css('width', '90px'); } else { $(this.logoDiv).addClass('aladin-logo-small'); $(this.logoDiv).removeClass('aladin-logo-large'); $(this.logoDiv).css('width', '32px'); } this.computeNorder(); //this.requestRedraw(); }; var pixelateCanvasContext = function(ctx, pixelateFlag) { var enableSmoothing = ! pixelateFlag; ctx.imageSmoothingEnabled = enableSmoothing; ctx.webkitImageSmoothingEnabled = enableSmoothing; ctx.mozImageSmoothingEnabled = enableSmoothing; ctx.msImageSmoothingEnabled = enableSmoothing; ctx.oImageSmoothingEnabled = enableSmoothing; } View.prototype.setMode = function(mode) { this.mode = mode; if (this.mode==View.SELECT) { this.setCursor('crosshair'); } else if (this.mode==View.TOOL_SIMBAD_POINTER) { this.popup.hide(); this.reticleCanvas.style.cursor = ''; $(this.reticleCanvas).addClass('aladin-sp-cursor'); } else { this.setCursor('default'); } }; View.prototype.setCursor = function(cursor) { if (this.reticleCanvas.style.cursor==cursor) { return; } if (this.mode==View.TOOL_SIMBAD_POINTER) { return; } this.reticleCanvas.style.cursor = cursor; }; /** * return dataURL string corresponding to the current view */ View.prototype.getCanvasDataURL = function(imgType, width, height) { imgType = imgType || "image/png"; var c = document.createElement('canvas'); width = width || this.width; height = height || this.height; c.width = width; c.height = height; var ctx = c.getContext('2d'); //ctx.drawImage(this.imageCanvas, 0, 0, c.width, c.height); const canvas = this.aladin.webglAPI.canvas(); ctx.drawImage(canvas, 0, 0, c.width, c.height); ctx.drawImage(this.catalogCanvas, 0, 0, c.width, c.height); ctx.drawImage(this.reticleCanvas, 0, 0, c.width, c.height); ctx.drawImage(this.gridCanvas, 0, 0, c.width, c.height); return c.toDataURL(imgType); //return c.toDataURL("image/jpeg", 0.01); // setting quality only works for JPEG (?) }; /** * Compute the FoV in degrees of the view and update mouseMoveIncrement * * @param view * @returns FoV (array of 2 elements : width and height) in degrees */ /* function computeFov(view) { var fov = doComputeFov(view, view.zoomFactor); view.mouseMoveIncrement = fov/view.imageCanvas.width; return fov; } function doComputeFov(view, zoomFactor) { // if zoom factor < 1, we view 180° var fov; if (view.zoomFactor<1) { fov = 180.0; //fov = 360; } else { // TODO : fov sur les 2 dimensions !! // to compute FoV, we first retrieve 2 points at coordinates (0, view.cy) and (width-1, view.cy) var xy1 = AladinUtils.viewToXy(0, view.cy, view.width, view.height, view.largestDim, zoomFactor); var lonlat1 = view.projection.unproject(xy1.x, xy1.y); var xy2 = AladinUtils.viewToXy(view.imageCanvas.width-1, view.cy, view.width, view.height, view.largestDim, zoomFactor); var lonlat2 = view.projection.unproject(xy2.x, xy2.y); fov = new Coo(lonlat1.ra, lonlat1.dec).distance(new Coo(lonlat2.ra, lonlat2.dec)); } fov = Math.min(180.0, fov); return fov; } */ function updateFovDiv(view) { if (isNaN(view.fov)) { view.fovDiv.html("FoV:"); return; } // update FoV value var fovStr; if (view.fov>1) { fovStr = Math.round(view.fov*100)/100 + "°"; } else if (view.fov*60>1) { fovStr = Math.round(view.fov*60*100)/100 + "'"; } else { fovStr = Math.round(view.fov*3600*100)/100 + '"'; } view.fovDiv.html("FoV: " + fovStr); } var createListeners = function(view) { var hasTouchEvents = false; if ('ontouchstart' in window) { hasTouchEvents = true; } // various listeners let onDblClick = function(e) { var xymouse = view.imageCanvas.relMouseCoords(e); if(view.aladin.webglAPI.posOnUi(xymouse.x, xymouse.y)) { return; } //var xy = AladinUtils.viewToXy(xymouse.x, xymouse.y, view.width, view.height, view.largestDim, view.zoomFactor); try { var lonlat = view.aladin.webglAPI.screenToWorld(xymouse.x, xymouse.y); } catch(err) { return; } var radec; /*if (view.aladin.webglAPI.cooSystem() === Aladin.wasmLibs.webgl.GALCooSys()) { radec = view.aladin.webglAPI.Gal2J2000(lonlat[0], lonlat[1]); } else {*/ radec = lonlat; //} //var radec = view.aladin.webglAPI.; // convert to J2000 if needed /*if (view.cooFrame.system==CooFrameEnum.SYSTEMS.GAL) { radec = CooConversion.GalacticToJ2000([lonlat.ra, lonlat.dec]); } else { radec = lonlat; }*/ view.pointTo(radec[0], radec[1], {forceAnimation: true}); }; if (! hasTouchEvents) { $(view.reticleCanvas).dblclick(onDblClick); } $(view.reticleCanvas).bind("mousedown touchstart", function(e) { var xymouse = view.imageCanvas.relMouseCoords(e); if(view.aladin.webglAPI.posOnUi(xymouse.x, xymouse.y)) { return; } // zoom pinching if (e.type==='touchstart' && e.originalEvent && e.originalEvent.targetTouches && e.originalEvent.targetTouches.length==2) { view.dragging = false; view.pinchZoomParameters.isPinching = true; //var fov = view.aladin.getFov(); //view.pinchZoomParameters.initialFov = Math.max(fov[0], fov[1]); var fov = view.aladin.webglAPI.getFieldOfView(); view.pinchZoomParameters.initialFov = fov; view.pinchZoomParameters.initialDistance = Math.sqrt(Math.pow(e.originalEvent.targetTouches[0].clientX - e.originalEvent.targetTouches[1].clientX, 2) + Math.pow(e.originalEvent.targetTouches[0].clientY - e.originalEvent.targetTouches[1].clientY, 2)); view.fingersRotationParameters.initialViewAngleFromCenter = view.aladin.webglAPI.getRotationAroundCenter(); view.fingersRotationParameters.initialFingerAngle = Math.atan2(e.originalEvent.targetTouches[1].clientY - e.originalEvent.targetTouches[0].clientY, e.originalEvent.targetTouches[1].clientX - e.originalEvent.targetTouches[0].clientX) * 180.0 / Math.PI; return; } var xymouse = view.imageCanvas.relMouseCoords(e); if (e.originalEvent && e.originalEvent.targetTouches) { view.dragx = e.originalEvent.targetTouches[0].clientX; view.dragy = e.originalEvent.targetTouches[0].clientY; } else { /* view.dragx = e.clientX; view.dragy = e.clientY; */ view.dragx = xymouse.x; view.dragy = xymouse.y; } view.dragging = true; if (view.mode==View.PAN) { view.setCursor('move'); } else if (view.mode==View.SELECT) { view.selectStartCoo = {x: view.dragx, y: view.dragy}; } view.aladin.webglAPI.pressLeftMouseButton(view.dragx, view.dragy); return false; // to disable text selection }); //$(view.reticleCanvas).bind("mouseup mouseout touchend", function(e) { $(view.reticleCanvas).bind("click mouseout touchend", function(e) { // reacting on 'click' rather on 'mouseup' is more reliable when panning the view var xymouse = view.imageCanvas.relMouseCoords(e); if (e.type==='touchend' && view.pinchZoomParameters.isPinching) { view.pinchZoomParameters.isPinching = false; view.pinchZoomParameters.initialFov = view.pinchZoomParameters.initialDistance = undefined; return; } if (e.type==='touchend' && view.fingersRotationParameters.rotationInitiated) { view.fingersRotationParameters.initialViewAngleFromCenter = undefined; view.fingersRotationParameters.initialFingerAngle = undefined; view.fingersRotationParameters.rotationInitiated = false; return; } var wasDragging = view.realDragging === true; var selectionHasEnded = view.mode===View.SELECT && view.dragging; if (view.dragging) { // if we were dragging, reset to default cursor view.setCursor('default'); view.dragging = false; if (wasDragging) { view.realDragging = false; // call positionChanged one last time after dragging, with dragging: false var posChangedFn = view.aladin.callbacksByEventName['positionChanged']; if (typeof posChangedFn === 'function') { var pos = view.aladin.pix2world(view.width/2, view.height/2); if (pos !== undefined) { posChangedFn({ra: pos[0], dec: pos[1], dragging: false}); } } } } // end of "if (view.dragging) ... " if (selectionHasEnded) { view.aladin.fire('selectend', view.getObjectsInBBox(view.selectStartCoo.x, view.selectStartCoo.y, view.dragx-view.selectStartCoo.x, view.dragy-view.selectStartCoo.y)); view.mustRedrawReticle = true; // pour effacer selection bounding box view.requestRedraw(); return; } view.mustClearCatalog = true; view.mustRedrawReticle = true; // pour effacer selection bounding box view.dragx = view.dragy = null; if (e.type==="mouseout" || e.type==="touchend") { view.requestRedraw(true); updateLocation(view, view.width/2, view.height/2, true); if (e.type==="mouseout") { if (view.mode===View.TOOL_SIMBAD_POINTER) { view.setMode(View.PAN); } return; } } if (view.mode==View.TOOL_SIMBAD_POINTER) { var radec = view.aladin.pix2world(xymouse.x, xymouse.y); view.setMode(View.PAN); view.setCursor('wait'); SimbadPointer.query(radec[0], radec[1], Math.min(1, 15 * view.fov / view.largestDim), view.aladin); return; // when in TOOL_SIMBAD_POINTER mode, we do not call the listeners } // popup to show ? var objs = view.closestObjects(xymouse.x, xymouse.y, 5); if (! wasDragging && objs) { var o = objs[0]; // footprint selection code adapted from Fabrizio Giordano dev. from Serco for ESA/ESDC if (o instanceof Footprint || o instanceof Circle) { o.dispatchClickEvent(); } // display marker else if (o.marker) { // could be factorized in Source.actionClicked view.popup.setTitle(o.popupTitle); view.popup.setText(o.popupDesc); view.popup.setSource(o); view.popup.show(); } // show measurements else { if (view.lastClickedObject) { view.lastClickedObject.actionOtherObjectClicked && view.lastClickedObject.actionOtherObjectClicked(); } o.actionClicked(); } view.lastClickedObject = o; var objClickedFunction = view.aladin.callbacksByEventName['objectClicked']; (typeof objClickedFunction === 'function') && objClickedFunction(o); } else { if (view.lastClickedObject && ! wasDragging) { view.aladin.measurementTable.hide(); view.popup.hide(); if (view.lastClickedObject instanceof Footprint) { //view.lastClickedObject.deselect(); } else { view.lastClickedObject.actionOtherObjectClicked(); } view.lastClickedObject = null; var objClickedFunction = view.aladin.callbacksByEventName['objectClicked']; (typeof objClickedFunction === 'function') && objClickedFunction(null); } } // call listener of 'click' event var onClickFunction = view.aladin.callbacksByEventName['click']; if (typeof onClickFunction === 'function') { var pos = view.aladin.pix2world(xymouse.x, xymouse.y); if (pos !== undefined) { onClickFunction({ra: pos[0], dec: pos[1], x: xymouse.x, y: xymouse.y, isDragging: wasDragging}); } } // TODO : remplacer par mecanisme de listeners // on avertit les catalogues progressifs view.refreshProgressiveCats(); view.requestRedraw(true); view.aladin.webglAPI.releaseLeftButtonMouse(xymouse.x, xymouse.y); }); var lastHoveredObject; // save last object hovered by mouse var lastMouseMovePos = null; var mouseOnUi = false; let webglAPI = view.aladin.webglAPI; let p = null; $(view.reticleCanvas).bind("mousemove touchmove", function(e) { e.preventDefault(); var xymouse = view.imageCanvas.relMouseCoords(e); p = xymouse; if(view.aladin.webglAPI.posOnUi(xymouse.x, xymouse.y)) { return; } if (e.type==='touchmove' && view.pinchZoomParameters.isPinching && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length==2) { // rotation var currentFingerAngle = Math.atan2(e.originalEvent.targetTouches[1].clientY - e.originalEvent.targetTouches[0].clientY, e.originalEvent.targetTouches[1].clientX - e.originalEvent.targetTouches[0].clientX) * 180.0 / Math.PI; var fingerAngleDiff = view.fingersRotationParameters.initialFingerAngle - currentFingerAngle; // rotation is initiated when angle is equal or greater than 7 degrees if (! view.fingersRotationParameters.rotationInitiated && Math.abs(fingerAngleDiff)>=7) { view.fingersRotationParameters.rotationInitiated = true; view.fingersRotationParameters.initialFingerAngle = currentFingerAngle; fingerAngleDiff = 0; } if (view.fingersRotationParameters.rotationInitiated) { view.aladin.webglAPI.setRotationAroundCenter(fingerAngleDiff + view.fingersRotationParameters.initialViewAngleFromCenter); } // zoom var dist = Math.sqrt(Math.pow(e.originalEvent.touches[0].clientX - e.originalEvent.touches[1].clientX, 2) + Math.pow(e.originalEvent.touches[0].clientY - e.originalEvent.touches[1].clientY, 2)); view.setZoom(view.pinchZoomParameters.initialFov * view.pinchZoomParameters.initialDistance / dist); return; } if (!view.dragging || hasTouchEvents) { // update location box updateLocation(view, xymouse.x, xymouse.y); // call listener of 'mouseMove' event var onMouseMoveFunction = view.aladin.callbacksByEventName['mouseMove']; if (typeof onMouseMoveFunction === 'function') { var pos = view.aladin.pix2world(xymouse.x, xymouse.y); if (pos !== undefined) { onMouseMoveFunction({ra: pos[0], dec: pos[1], x: xymouse.x, y: xymouse.y}); } // send null ra and dec when we go out of the "sky" else if (lastMouseMovePos != null) { onMouseMoveFunction({ra: null, dec: null, x: xymouse.x, y: xymouse.y}); } lastMouseMovePos = pos; } if (!view.dragging && ! view.mode==View.SELECT) { // objects under the mouse ? var closest = view.closestObjects(xymouse.x, xymouse.y, 5); if (closest) { view.setCursor('pointer'); var objHoveredFunction = view.aladin.callbacksByEventName['objectHovered']; if (typeof objHoveredFunction === 'function' && closest[0]!=lastHoveredObject) { var ret = objHoveredFunction(closest[0]); } lastHoveredObject = closest[0]; } else { view.setCursor('default'); var objHoveredFunction = view.aladin.callbacksByEventName['objectHovered']; if (typeof objHoveredFunction === 'function' && lastHoveredObject) { lastHoveredObject = null; // call callback function to notify we left the hovered object var ret = objHoveredFunction(null); } } } if (!hasTouchEvents) { return; } } if (! view.dragging) { return; } //var xoffset, yoffset; var s1, s2; if (e.originalEvent && e.originalEvent.targetTouches) { /*xoffset = e.originalEvent.targetTouches[0].clientX-view.dragx; yoffset = e.originalEvent.targetTouches[0].clientY-view.dragy; var xy1 = AladinUtils.viewToXy(e.originalEvent.targetTouches[0].clientX, e.originalEvent.targetTouches[0].clientY, view.width, view.height, view.largestDim, view.zoomFactor); var xy2 = AladinUtils.viewToXy(view.dragx, view.dragy, view.width, view.height, view.largestDim, view.zoomFactor); pos1 = view.projection.unproject(xy1.x, xy1.y); pos2 = view.projection.unproject(xy2.x, xy2.y);*/ s1 = {x: view.dragx, y: view.dragy}; s2 = {x: e.originalEvent.targetTouches[0].clientX, y: e.originalEvent.targetTouches[0].clientY}; } else { /* xoffset = e.clientX-view.dragx; yoffset = e.clientY-view.dragy; xoffset = xymouse.x-view.dragx; yoffset = xymouse.y-view.dragy; var xy1 = AladinUtils.viewToXy(xymouse.x, xymouse.y, view.width, view.height, view.largestDim, view.zoomFactor); var xy2 = AladinUtils.viewToXy(view.dragx, view.dragy, view.width, view.height, view.largestDim, view.zoomFactor); */ //pos1 = view.projection.unproject(xy1.x, xy1.y); //pos2 = view.projection.unproject(xy2.x, xy2.y); //console.log(view.dragx, view.dragy) //console.log(xymouse) /*pos1 = webglAPI.screenToWorld(view.dragx, view.dragy); pos2 = webglAPI.screenToWorld(xymouse.x, xymouse.y); if (pos2 == undefined) { return; }*/ s1 = {x: view.dragx, y: view.dragy}; s2 = {x: xymouse.x, y: xymouse.y}; } // TODO : faut il faire ce test ?? // var distSquared = xoffset*xoffset+yoffset*yoffset; // if (distSquared<3) { // return; // } if (e.originalEvent && e.originalEvent.targetTouches) { view.dragx = e.originalEvent.targetTouches[0].clientX; view.dragy = e.originalEvent.targetTouches[0].clientY; } else { view.dragx = xymouse.x; view.dragy = xymouse.y; /* view.dragx = e.clientX; view.dragy = e.clientY; */ } if (view.mode==View.SELECT) { view.requestRedraw(); return; } //view.viewCenter.lon += xoffset*view.mouseMoveIncrement/Math.cos(view.viewCenter.lat*Math.PI/180.0); /* view.viewCenter.lon += xoffset*view.mouseMoveIncrement; view.viewCenter.lat += yoffset*view.mouseMoveIncrement; */ //view.viewCenter.lon = pos2.ra - pos1.ra; //view.viewCenter.lat = pos2.dec - pos1.dec; //view.viewCenter.lon = pos2.ra; //view.viewCenter.lon = pos2.ra; // can not go beyond poles if (view.viewCenter.lat>90) { view.viewCenter.lat = 90; } else if (view.viewCenter.lat < -90) { view.viewCenter.lat = -90; } // limit lon to [0, 360] if (view.viewCenter.lon < 0) { view.viewCenter.lon = 360 + view.viewCenter.lon; } else if (view.viewCenter.lon > 360) { view.viewCenter.lon = view.viewCenter.lon % 360; } view.realDragging = true; //webglAPI.goFromTo(pos1[0], pos1[1], pos2[0], pos2[1]); webglAPI.goFromTo(s1.x, s1.y, s2.x, s2.y); //webglAPI.setCenter(pos2[0], pos2[1]); let viewCenter = webglAPI.getCenter(); view.viewCenter.lon = viewCenter[0]; view.viewCenter.lat = viewCenter[1]; //console.log(view.viewCenter); view.requestRedraw(); }); //// endof mousemove //// // disable text selection on IE $(view.aladinDiv).onselectstart = function () { return false; } $(view.reticleCanvas).on('wheel', function(event) { event.preventDefault(); event.stopPropagation(); //var xymouse = view.imageCanvas.relMouseCoords(event); if(view.aladin.webglAPI.posOnUi(p.x, p.y)) { return; } //var xymouse = view.imageCanvas.relMouseCoords(event); //if(mouseOnUi) {return;} //var level = view.zoomLevel; var delta = event.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 (event.hasOwnProperty('originalEvent')) { delta = -event.originalEvent.deltaY; } /*if (delta>0) { level += 1; //zoom } else { level -= 1; //unzoom }*/ // The value of the field of view is determined // inside the backend view.aladin.webglAPI.registerWheelEvent(delta); view.updateZoomState(); if (! view.debounceProgCatOnZoom) { var self = view; view.debounceProgCatOnZoom = Utils.debounce(function() {self.refreshProgressiveCats();}, 300); } view.debounceProgCatOnZoom(); //view.setZoomLevel(level); //view.refreshProgressiveCats(); return false; }); }; var init = function(view) { var stats = new Stats(); stats.domElement.style.top = '50px'; if ($('#aladin-statsDiv').length>0) { $('#aladin-statsDiv')[0].appendChild( stats.domElement ); } view.stats = stats; createListeners(view); view.executeCallbacksThrottled = Utils.throttle( function() { var pos = view.aladin.pix2world(view.width/2, view.height/2); var fov = view.fov; if (pos===undefined || fov===undefined) { return; } var ra = pos[0]; var dec = pos[1]; // trigger callback only if position has changed ! if (ra!==this.ra || dec!==this.dec) { var posChangedFn = view.aladin.callbacksByEventName['positionChanged']; (typeof posChangedFn === 'function') && posChangedFn({ra: ra, dec: dec, dragging: true}); // finally, save ra and dec value this.ra = ra; this.dec = dec; } // trigger callback only if FoV (zoom) has changed ! if (fov!==this.old_fov) { var fovChangedFn = view.aladin.callbacksByEventName['zoomChanged']; (typeof fovChangedFn === 'function') && fovChangedFn(fov); // finally, save fov value this.old_fov = fov; } }, View.CALLBACKS_THROTTLE_TIME_MS); view.displayHpxGrid = false; view.displaySurvey = true; view.displayCatalog = false; view.displayReticle = true; // initial draw //view.fov = computeFov(view); //updateFovDiv(view); //view.redraw(); }; function updateLocation(view, x, y, isViewCenterPosition) { if (!view.projection) { return; } //var xy = AladinUtils.viewToXy(x, y, view.width, view.height, view.largestDim, view.zoomFactor); var lonlat; try { lonlat = view.aladin.webglAPI.screenToWorld(x, y); } catch(err) { } if (lonlat) { // Convert it to galactic if (view.aladin.webglAPI.cooSystem() === Aladin.wasmLibs.webgl.GALCooSys()) { lonlat = view.aladin.webglAPI.J20002Gal(lonlat[0], lonlat[1]); } //console.log(view.aladin.webglAPI.readPixel(x, y, 'base')); view.location.update(lonlat[0], lonlat[1], view.cooFrame, isViewCenterPosition); } } View.prototype.requestRedrawAtDate = function(date) { this.dateRequestDraw = date; }; /** * Return the color of the lowest intensity pixel * in teh current color map of the current background image HiPS */ View.prototype.getBackgroundColor = function() { var white = 'rgb(255, 255, 255)'; var black = 'rgb(0, 0, 0)'; if (! this.imageSurvey) { return black; } var cm = this.imageSurvey.getColorMap(); if (!cm) { return black; } if (cm.mapName == 'native' || cm.mapName == 'grayscale') { return cm.reversed ? white : black; } var idx = cm.reversed ? 255 : 0; var r = ColorMap.MAPS[cm.mapName].r[idx]; var g = ColorMap.MAPS[cm.mapName].g[idx]; var b = ColorMap.MAPS[cm.mapName].b[idx]; return 'rgb(' + r + ',' + g + ',' + b + ')'; }; View.prototype.getViewParams = function() { var resolution = this.width > this.height ? this.fov / this.width : this.fov / this.height; return { fov: [this.width * resolution, this.height * resolution], width: this.width, height: this.height }; }; /** * redraw the whole view */ View.prototype.redraw = function() { var saveNeedRedraw = this.needRedraw; var now = Date.now(); var dt = now - this.prev; this.ready = this.aladin.webglAPI.isReady(); if (this.imageSurveysToSet !== null && (this.firstHiPS || this.ready)) { try { this.aladin.webglAPI.setImageSurveys(this.imageSurveysToSet); } catch(e) { console.warn(e) } this.imageSurveysToSet = null; this.firstHiPS = false; } this.aladin.webglAPI.update(dt, this.needRedraw); // This is called at each frame // Better way is to give this function // to Rust so that the backend executes it // only when necessary, i.e. during the zoom // animation updateFovDiv(this); // check whether a catalog has been parsed and // is ready to be plot let catReady = this.aladin.webglAPI.isCatalogLoaded(); if (catReady) { var callbackFn = this.aladin.callbacksByEventName['catalogReady']; (typeof callbackFn === 'function') && callbackFn(); } this.aladin.webglAPI.render(this.needRedraw); var imageCtx = this.imageCtx; //////// 1. Draw images //////// /*if (imageCtx.start2D) { imageCtx.start2D(); }*/ //// clear canvas //// // TODO : do not need to clear if fov small enough ? /*imageCtx.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height); //////////////////////// var bkgdColor = this.getBackgroundColor(); // fill with background of the same color than the first color map value (lowest intensity) if (this.projectionMethod==ProjectionEnum.SIN) { if (this.fov>=60) { imageCtx.fillStyle = bkgdColor; imageCtx.beginPath(); var maxCxCy = this.cx>this.cy ? this.cx : this.cy; imageCtx.arc(this.cx, this.cy, maxCxCy * this.zoomFactor, 0, 2*Math.PI, true); imageCtx.fill(); } // pour eviter les losanges blancs qui apparaissent quand les tuiles sont en attente de chargement else { imageCtx.fillStyle = bkgdColor; imageCtx.fillRect(0, 0, this.imageCanvas.width, this.imageCanvas.height); } } else if (this.projectionMethod==ProjectionEnum.AITOFF) { if (imageCtx.ellipse) { imageCtx.fillStyle = bkgdColor; imageCtx.beginPath(); imageCtx.ellipse(this.cx, this.cy, 2.828*this.cx*this.zoomFactor, this.cx*this.zoomFactor*1.414, 0, 0, 2*Math.PI); imageCtx.fill(); } }*/ /*if (imageCtx.finish2D) { imageCtx.finish2D(); }*/ this.projection.setCenter(this.viewCenter.lon, this.viewCenter.lat); // do we have to redo that every time? Probably not //this.projection.setProjection(this.projectionMethod); // ************* Draw allsky tiles (low resolution) ***************** var cornersXYViewMapHighres = null; // Pour traitement des DEFORMATIONS --> TEMPORAIRE, draw deviendra la methode utilisee systematiquement /*if (this.imageSurvey && this.imageSurvey.isReady && this.displaySurvey) { if (this.aladin.reduceDeformations==null) { this.imageSurvey.draw(imageCtx, this, !this.dragging, this.curNorder); } else { this.imageSurvey.draw(imageCtx, this, this.aladin.reduceDeformations, this.curNorder); } }*/ /* else { var cornersXYViewMapAllsky = this.getVisibleCells(3); var cornersXYViewMapHighres = null; if (this.curNorder>=3) { if (this.curNorder==3) { cornersXYViewMapHighres = cornersXYViewMapAllsky; } else { cornersXYViewMapHighres = this.getVisibleCells(this.curNorder); } } // redraw image survey if (this.imageSurvey && this.imageSurvey.isReady && this.displaySurvey) { // TODO : a t on besoin de dessiner le allsky si norder>=3 ? // TODO refactoring : should be a method of HpxImageSurvey this.imageSurvey.redrawAllsky(imageCtx, cornersXYViewMapAllsky, this.fov, this.curNorder); if (this.curNorder>=3) { this.imageSurvey.redrawHighres(imageCtx, cornersXYViewMapHighres, this.curNorder); } } } */ // redraw overlay image survey // TODO : does not work if different frames // TODO: use HpxImageSurvey.draw method !! if (this.overlayImageSurvey && this.overlayImageSurvey.isReady) { /*imageCtx.globalAlpha = this.overlayImageSurvey.getAlpha(); if (this.aladin.reduceDeformations==null) { this.overlayImageSurvey.draw(imageCtx, this, !this.dragging, this.curOverlayNorder); } else { this.overlayImageSurvey.draw(imageCtx, this, this.aladin.reduceDeformations, this.curOverlayNorder); }*/ /* if (this.fov>50) { this.overlayImageSurvey.redrawAllsky(imageCtx, cornersXYViewMapAllsky, this.fov, this.curOverlayNorder); } if (this.curOverlayNorder>=3) { var norderOverlay = Math.min(this.curOverlayNorder, this.overlayImageSurvey.maxOrder); if ( cornersXYViewMapHighres==null || norderOverlay != this.curNorder ) { cornersXYViewMapHighres = this.getVisibleCells(norderOverlay); } this.overlayImageSurvey.redrawHighres(imageCtx, cornersXYViewMapHighres, norderOverlay); } */ //imageCtx.globalAlpha = 1.0; } // redraw HEALPix grid if( this.displayHpxGrid) { var cornersXYViewMapAllsky = this.getVisibleCells(3); var cornersXYViewMapHighres = null; if (this.curNorder>=3) { if (this.curNorder==3) { cornersXYViewMapHighres = cornersXYViewMapAllsky; } else { cornersXYViewMapHighres = this.getVisibleCells(this.curNorder); } } this.gridCtx.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height); if (cornersXYViewMapHighres && this.curNorder>3) { this.healpixGrid.redraw(this.gridCtx, cornersXYViewMapHighres, this.fov, this.curNorder); } else { this.healpixGrid.redraw(this.gridCtx, cornersXYViewMapAllsky, this.fov, 3); } } // redraw coordinates grid /*if (this.showGrid) { if (this.cooGrid==null) { this.cooGrid = new CooGrid(); } this.cooGrid.redraw(this.gridCtx, this.projection, this.cooFrame, this.width, this.height, this.largestDim, this.zoomFactor, this.fov); }*/ ///* ////// 2. Draw catalogues//////// var catalogCtx = this.catalogCtx; var catalogCanvasCleared = false; if (this.mustClearCatalog) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; this.mustClearCatalog = false; } if (this.catalogs && this.catalogs.length>0 && this.displayCatalog && (! this.dragging || View.DRAW_SOURCES_WHILE_DRAGGING)) { // TODO : do not clear every time //// clear canvas //// if (! catalogCanvasCleared) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; } for (var i=0; i0) { if (! catalogCanvasCleared) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; } this.catalogForPopup.draw(catalogCtx, this.projection, this.cooFrame, this.width, this.height, this.largestDim, this.zoomFactor); } ////// 3. Draw overlays//////// var overlayCtx = this.catalogCtx; if (this.overlays && this.overlays.length>0 && (! this.dragging || View.DRAW_SOURCES_WHILE_DRAGGING)) { if (! catalogCanvasCleared) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; } for (var i=0; i0 && (! this.dragging || View.DRAW_MOCS_WHILE_DRAGGING)) { if (! catalogCanvasCleared) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; } for (var i=0; i=60 && this.aladin.options['showAllskyRing'] === true) { imageCtx.strokeStyle = this.aladin.options['allskyRingColor']; var ringWidth = this.aladin.options['allskyRingWidth']; imageCtx.lineWidth = ringWidth; imageCtx.beginPath(); var maxCxCy = this.cx>this.cy ? this.cx : this.cy; imageCtx.arc(this.cx, this.cy, (maxCxCy-(ringWidth/2.0)+1) * this.zoomFactor, 0, 2*Math.PI, true); imageCtx.stroke(); } // draw selection box if (this.mode==View.SELECT && this.dragging) { reticleCtx.fillStyle = "rgba(100, 240, 110, 0.25)"; var w = this.dragx - this.selectStartCoo.x; var h = this.dragy - this.selectStartCoo.y; reticleCtx.fillRect(this.selectStartCoo.x, this.selectStartCoo.y, w, h); } */ // TODO : is this the right way? if (saveNeedRedraw==this.needRedraw) { this.needRedraw = false; } // objects lookup if (!this.dragging) { this.updateObjectsLookup(); } // execute 'positionChanged' and 'zoomChanged' callbacks //this.executeCallbacksThrottled(); this.prev = now; requestAnimFrame(this.redraw.bind(this)); }; /*View.prototype.drawGridLabels = function (text) { //let ctx = this.imageCanvas.getContext("webgl2"); //var c = document.getElementById("myCanvas"); //var ctx = c.getContext("2d"); this.reticleCtx.font = "30px Verdana"; this.reticleCtx.fillText(text, 200, 50); //ctx.font = "30px Verdana"; }*/ View.prototype.forceRedraw = function() { this.flagForceRedraw = true; }; View.prototype.refreshProgressiveCats = function() { if (! this.catalogs) { return; } for (var i=0; i80) { pixList = []; for (var ipix=0; ipix60) { radius *= 1.6; } else if (this.fov>12) { radius *=1.45; } else { radius *= 1.1; } pixList = hpxIdx.queryDisc(spatialVector, radius*Math.PI/180.0, true, true); // add central pixel at index 0 var polar = HealpixIndex.utils.radecToPolar(lonlat[0], lonlat[1]); var ipixCenter = hpxIdx.ang2pix_nest(polar.theta, polar.phi); pixList.unshift(ipixCenter); } return pixList; }; View.prototype.setAngleRotation = function(theta) { } // TODO: optimize this method !! View.prototype.getVisibleCells = function(norder, frameSurvey) { if (! frameSurvey && this.imageSurvey) { frameSurvey = this.imageSurvey.cooFrame; } var cells = []; // array to be returned var cornersXY = []; var spVec = new SpatialVector(); var nside = Math.pow(2, norder); // TODO : to be modified var npix = HealpixIndex.nside2Npix(nside); var ipixCenter = null; // build list of pixels // TODO: pixList can be obtained from getVisiblePixList var pixList; if (this.fov>80) { pixList = []; for (var ipix=0; ipix60) { radius *= 1.6; } else if (this.fov>12) { radius *=1.45; } else { radius *= 1.1; } pixList = hpxIdx.queryDisc(spatialVector, radius*Math.PI/180.0, true, true); // add central pixel at index 0 var polar = HealpixIndex.utils.radecToPolar(lonlat[0], lonlat[1]); ipixCenter = hpxIdx.ang2pix_nest(polar.theta, polar.phi); pixList.unshift(ipixCenter); } var ipix; var lon, lat; var corners; for (var ipixIdx=0, len=pixList.length; ipixIdx0) { continue; } var cornersXYView = []; corners = HealpixCache.corners_nest(ipix, nside); for (var k=0; k<4; k++) { spVec.setXYZ(corners[k].x, corners[k].y, corners[k].z); // need for frame transformation ? if (frameSurvey && frameSurvey.system != this.cooFrame.system) { if (frameSurvey.system == CooFrameEnum.SYSTEMS.J2000) { var radec = CooConversion.J2000ToGalactic([spVec.ra(), spVec.dec()]); lon = radec[0]; lat = radec[1]; } else if (frameSurvey.system == CooFrameEnum.SYSTEMS.GAL) { var radec = CooConversion.GalacticToJ2000([spVec.ra(), spVec.dec()]); lon = radec[0]; lat = radec[1]; } } else { lon = spVec.ra(); lat = spVec.dec(); } //cornersXY[k] = this.projection.project(lon, lat); cornersXY[k] = this.aladin.webglAPI.worldToScreen(lon, lat); } if (cornersXY[0] == null || cornersXY[1] == null || cornersXY[2] == null || cornersXY[3] == null ) { continue; } for (var k=0; k<4; k++) { //cornersXYView[k] = AladinUtils.xyToView(cornersXY[k].X, cornersXY[k].Y, this.width, this.height, this.largestDim, this.zoomFactor); cornersXYView[k] = { vx: cornersXY[k][0], vy: cornersXY[k][1], }; } var indulge = 10; // detect pixels outside view. Could be improved ! // we minimize here the number of cells returned if( cornersXYView[0].vx<0 && cornersXYView[1].vx<0 && cornersXYView[2].vx<0 &&cornersXYView[3].vx<0) { continue; } if( cornersXYView[0].vy<0 && cornersXYView[1].vy<0 && cornersXYView[2].vy<0 &&cornersXYView[3].vy<0) { continue; } if( cornersXYView[0].vx>=this.width && cornersXYView[1].vx>=this.width && cornersXYView[2].vx>=this.width &&cornersXYView[3].vx>=this.width) { continue; } if( cornersXYView[0].vy>=this.height && cornersXYView[1].vy>=this.height && cornersXYView[2].vy>=this.height &&cornersXYView[3].vy>=this.height) { continue; } // check if pixel is visible // if (this.fov<160) { // don't bother checking if fov is large enough // if ( ! AladinUtils.isHpxPixVisible(cornersXYView, this.width, this.height) ) { // continue; // } // } // check if we have a pixel at the edge of the view in allsky projections if (this.projection.PROJECTION!=ProjectionEnum.SIN && this.projection.PROJECTION!=ProjectionEnum.TAN) { /* console.log(this.largestDim); var xdiff = cornersXYView[0].vx-cornersXYView[2].vx; var ydiff = cornersXYView[0].vy-cornersXYView[2].vy; var distDiag = Math.sqrt(xdiff*xdiff + ydiff*ydiff); if (distDiag>this.largestDim/5) { continue; } xdiff = cornersXYView[1].vx-cornersXYView[3].vx; ydiff = cornersXYView[1].vy-cornersXYView[3].vy; distDiag = Math.sqrt(xdiff*xdiff + ydiff*ydiff); if (distDiag>this.largestDim/5) { continue; }*/ // New faster approach: when a vertex from a cell gets to the other side of the projection // its vertices order change from counter-clockwise to clockwise! // So if the vertices describing a cell are given in clockwise order // we know it crosses the projection, so we do not plot them! if (!AladinUtils.counterClockwiseTriangle(cornersXYView[0].vx, cornersXYView[0].vy, cornersXYView[1].vx, cornersXYView[1].vy, cornersXYView[2].vx, cornersXYView[2].vy) || !AladinUtils.counterClockwiseTriangle(cornersXYView[0].vx, cornersXYView[0].vy, cornersXYView[2].vx, cornersXYView[2].vy, cornersXYView[3].vx, cornersXYView[3].vy)) { continue; } } cornersXYView.ipix = ipix; cells.push(cornersXYView); } return cells; }; // get position in view for a given HEALPix cell View.prototype.getPositionsInView = function(ipix, norder) { var cornersXY = []; var lon, lat; var spVec = new SpatialVector(); var nside = Math.pow(2, norder); // TODO : to be modified var cornersXYView = []; // will be returned var corners = HealpixCache.corners_nest(ipix, nside); for (var k=0; k<4; k++) { spVec.setXYZ(corners[k].x, corners[k].y, corners[k].z); // need for frame transformation ? if (this.imageSurvey && this.imageSurvey.cooFrame.system != this.cooFrame.system) { if (this.imageSurvey.cooFrame.system == CooFrameEnum.SYSTEMS.J2000) { var radec = CooConversion.J2000ToGalactic([spVec.ra(), spVec.dec()]); lon = radec[0]; lat = radec[1]; } else if (this.imageSurvey.cooFrame.system == CooFrameEnum.SYSTEMS.GAL) { var radec = CooConversion.GalacticToJ2000([spVec.ra(), spVec.dec()]); lon = radec[0]; lat = radec[1]; } } else { lon = spVec.ra(); lat = spVec.dec(); } //cornersXY[k] = this.projection.project(lon, lat); let xy = this.aladin.webglAPI.worldToScreen(lon, lat); cornersXYView[k] = { vx: xy.x, vy: xy.y }; } if (cornersXYView[0] == null || cornersXYView[1] == null || cornersXYView[2] == null || cornersXYView[3] == null ) { return null; } /*if (cornersXY[0] == null || cornersXY[1] == null || cornersXY[2] == null || cornersXY[3] == null ) { return null; }*/ /*for (var k=0; k<4; k++) { cornersXYView[k] = AladinUtils.xyToView(cornersXY[k].X, cornersXY[k].Y, this.width, this.height, this.largestDim, this.zoomFactor); }*/ return cornersXYView; }; /*View.prototype.computeZoomFactor = function(level) { if (level>0) { return AladinUtils.getZoomFactorForAngle(180.0/Math.pow(1.35, level), this.projectionMethod); } else { return 1 + 0.1*level; } };*/ /*View.prototype.computeZoomLevelFromFOV = function() { if (level>0) { return AladinUtils.getZoomFactorForAngle(180/Math.pow(1.15, level), this.projectionMethod); } else { return 1 + 0.1*level; } };*/ // Called for touchmove events View.prototype.setZoom = function(fovDegrees) { if (fovDegrees<0) { return; } // Erase the field of view state of the backend by this.aladin.webglAPI.setFieldOfView(fovDegrees); //var zoomLevel = Math.log(180/fovDegrees)/Math.log(1.15); //this.setZoomLevel(zoomLevel); this.updateZoomState(); }; View.prototype.increaseZoom = function() { for (let i = 0; i < 5; i++) { this.aladin.webglAPI.registerWheelEvent(0.01); } } View.prototype.decreaseZoom = function() { for (let i = 0; i < 5; i++) { this.aladin.webglAPI.registerWheelEvent(-0.01); } } View.prototype.setShowGrid = function(showGrid) { this.showGrid = showGrid; if (showGrid) { this.aladin.webglAPI.enableGrid(); } else { this.aladin.webglAPI.disableGrid(); } this.requestRedraw(); }; //View.prototype.setZoom = function(level) { View.prototype.updateZoomState = function() { /*let zoom = {"action": undefined}; if (this.zoomLevel > level) { console.log("unzoom") zoom["action"] = "unzoom"; } else if (this.zoomLevel < level) { zoom["action"] = "zoom"; }*/ /*if (this.minFOV || this.maxFOV) { var newFov = doComputeFov(this, this.computeZoomFactor(Math.max(-2, level))); if (this.maxFOV && newFov>this.maxFOV || this.minFOV && newFov= 1.0) { this.aladin.webglAPI.setFieldOfView(this.fov); } else { //console.log("FOV, ", this.fov / this.zoomFactor); // zoom factor this.aladin.webglAPI.setFieldOfView(this.fov / this.zoomFactor); }*/ this.zoomFactor = this.aladin.webglAPI.getClipZoomFactor(); this.fov = this.aladin.webglAPI.getFieldOfView(); // TODO: event/listener should be better //updateFovDiv(this); this.computeNorder(); this.forceRedraw(); //this.requestRedraw(); // on avertit les catalogues progressifs }; /** * compute and set the norder corresponding to the current view resolution */ View.prototype.computeNorder = function() { var resolution = this.fov / this.largestDim; // in degree/pixel var tileSize = 512; // TODO : read info from HpxImageSurvey.tileSize var nside = HealpixIndex.calculateNSide(3600*tileSize*resolution); // 512 = size of a "tile" image var norder = Math.log(nside)/Math.log(2); norder = Math.max(norder, 1); this.realNorder = norder; // here, we force norder to 3 (otherwise, the display is "blurry" for too long when zooming in) if (this.fov<=50 && norder<=2) { norder = 3; } // that happens if we do not wish to display tiles coming from Allsky.[jpg|png] if (this.imageSurvey && norder<=2 && this.imageSurvey.minOrder>2) { norder = this.imageSurvey.minOrder; } var overlayNorder = norder; if (this.imageSurvey && norder>this.imageSurvey.maxOrder) { norder = this.imageSurvey.maxOrder; } if (this.overlayImageSurvey && overlayNorder>this.overlayImageSurvey.maxOrder) { overlayNorder = this.overlayImageSurvey.maxOrder; } // should never happen, as calculateNSide will return something <=HealpixIndex.ORDER_MAX if (norder>HealpixIndex.ORDER_MAX) { norder = HealpixIndex.ORDER_MAX; } if (overlayNorder>HealpixIndex.ORDER_MAX) { overlayNorder = HealpixIndex.ORDER_MAX; } this.curNorder = norder; this.curOverlayNorder = overlayNorder; }; View.prototype.untaintCanvases = function() { this.createCanvases(); createListeners(this); this.fixLayoutDimensions(); }; View.prototype.setOverlayImageSurvey = async function(idOrUrl) { /*if (! overlayImageSurvey) { this.overlayImageSurvey = null; this.requestRedraw(); return; } // reset canvas to "untaint" canvas if needed // we test if the previous base image layer was using CORS or not if ($.support.cors && this.overlayImageSurvey && ! this.overlayImageSurvey.useCors) { this.untaintCanvases(); } var newOverlayImageSurvey; if (typeof overlayImageSurvey == "string") { newOverlayImageSurvey = HpxImageSurvey.getSurveyFromId(overlayImageSurvey); if ( ! newOverlayImageSurvey) { newOverlayImageSurvey = HpxImageSurvey.getSurveyFromId(HpxImageSurvey.DEFAULT_SURVEY_ID); } } else { newOverlayImageSurvey = overlayImageSurvey; } newOverlayImageSurvey.isReady = false; this.overlayImageSurvey = newOverlayImageSurvey; var self = this; newOverlayImageSurvey.init(this, function() { //self.imageSurvey = newImageSurvey; self.computeNorder(); newOverlayImageSurvey.isReady = true; self.requestRedraw(); self.updateObjectsLookup(); if (callback) { callback(); } });*/ if (!idOrUrl) { return; } let overlaySurvey = await new HpxImageSurvey(idOrUrl); this.aladin.webglAPI.setOverlayHiPS(overlaySurvey); }; View.prototype.setUnknownSurveyIfNeeded = function() { if (unknownSurveyId) { this.setImageSurvey(unknownSurveyId); unknownSurveyId = undefined; } } /*View.prototype.addImageSurveyLayer = function(layer) { if (!(layer instanceof ImageSurveyLayer)) { throw "Except an ImageSurveyLayer object"; } let surveys = []; for (let survey of layer.getSurveys()) { surveys.push(survey); } console.log("set layer: ", layer); this.aladin.webglAPI.addImageSurveyLayer(layer.name, surveys); };*/ var unknownSurveyId = undefined; // @param imageSurvey : HpxImageSurvey object or image survey identifier View.prototype.addImageSurvey = function(survey, layer) { // We wait for the HpxImageSurvey to complete // Register to the view const url = survey.properties.url; survey.layer = layer; this.imageSurveys.get(layer).set(url, survey); // Then we send the current surveys to the backend this.setHiPS(); }; View.prototype.setImageSurvey = function(survey, layer) { const url = survey.properties.url; survey.layer = layer; this.imageSurveys.set(layer, new Map()); this.imageSurveys.get(layer).set(url, survey); // Then we send the current surveys to the backend this.setHiPS(); }; View.prototype.setImageSurveysLayer = function(surveys, layer) { this.imageSurveys.set(layer, new Map()); surveys.forEach(survey => { const url = survey.properties.url; survey.layer = layer; this.imageSurveys.get(layer).set(url, survey); }); // Then we send the current surveys to the backend this.setHiPS(); }; View.prototype.removeImageSurveysLayer = function (layer) { this.imageSurveys.delete(layer); this.setHiPS(); }; View.prototype.moveImageSurveysLayerForward = function(layer) { this.aladin.webglAPI.moveImageSurveysLayerForward(layer); } View.prototype.setHiPS = function() { let surveys = []; for (let layer of this.imageSurveys.values()) { for (let survey of layer.values()) { surveys.push(survey); } } this.imageSurveysToSet = surveys; }; View.prototype.requestRedraw = function() { this.needRedraw = true; }; View.prototype.changeProjection = function(projectionName) { switch (projectionName) { case "aitoff": this.projectionMethod = ProjectionEnum.AITOFF; break; case "tan": this.projectionMethod = ProjectionEnum.TAN; break; case "arc": this.projectionMethod = ProjectionEnum.ARC; break; case "mercator": this.projectionMethod = ProjectionEnum.MERCATOR; break; case "mollweide": this.projectionMethod = ProjectionEnum.MOL; break; case "sinus": default: this.projectionMethod = ProjectionEnum.SIN; } // Change the projection here this.projection.setProjection(this.projectionMethod); this.aladin.webglAPI.setProjection(projectionName); this.requestRedraw(); }; View.prototype.changeFrame = function(cooFrame) { var oldCooFrame = this.cooFrame; this.cooFrame = cooFrame; // recompute viewCenter console.log("change frame") if (this.cooFrame.system == CooFrameEnum.SYSTEMS.GAL && this.cooFrame.system != oldCooFrame.system) { var lb = CooConversion.J2000ToGalactic([this.viewCenter.lon, this.viewCenter.lat]); this.viewCenter.lon = lb[0]; this.viewCenter.lat = lb[1]; const GAL = Aladin.wasmLibs.webgl.GALCooSys(); this.aladin.webglAPI.setCooSystem(GAL); } else if (this.cooFrame.system == CooFrameEnum.SYSTEMS.J2000 && this.cooFrame.system != oldCooFrame.system) { var radec = CooConversion.GalacticToJ2000([this.viewCenter.lon, this.viewCenter.lat]); this.viewCenter.lon = radec[0]; this.viewCenter.lat = radec[1]; const ICRSJ2000 = Aladin.wasmLibs.webgl.ICRSJ2000CooSys(); this.aladin.webglAPI.setCooSystem(ICRSJ2000); } this.location.update(this.viewCenter.lon, this.viewCenter.lat, this.cooFrame, true); this.requestRedraw(); }; View.prototype.showHealpixGrid = function(show) { // Clear the grid ctx when not showing it if (!show) { this.gridCtx.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height); } this.displayHpxGrid = show; this.requestRedraw(); }; View.prototype.showSurvey = function(show) { this.displaySurvey = show; this.requestRedraw(); }; View.prototype.showCatalog = function(show) { this.displayCatalog = show; if (!this.displayCatalog) { this.mustClearCatalog = true; } this.requestRedraw(); }; View.prototype.showReticle = function(show) { this.displayReticle = show; this.mustRedrawReticle = true; this.requestRedraw(); }; View.prototype.pointTo = function(ra, dec, options) { options = options || {}; ra = parseFloat(ra); dec = parseFloat(dec); if (isNaN(ra) || isNaN(dec)) { return; } //if (this.cooFrame.system==CooFrameEnum.SYSTEMS.J2000) { this.viewCenter.lon = ra; this.viewCenter.lat = dec; //} /*else if (this.cooFrame.system==CooFrameEnum.SYSTEMS.GAL) { var lb = CooConversion.J2000ToGalactic([ra, dec]); this.viewCenter.lon = lb[0]; this.viewCenter.lat = lb[1]; }*/ this.location.update(this.viewCenter.lon, this.viewCenter.lat, this.cooFrame, true); if (options && options.forceAnimation === false) { this.aladin.webglAPI.setCenter(this.viewCenter.lon, this.viewCenter.lat); } else if (options && options.forceAnimation === true) { this.aladin.webglAPI.moveToLocation(this.viewCenter.lon, this.viewCenter.lat) } else { if (this.fov > 30.0) { this.aladin.webglAPI.moveToLocation(this.viewCenter.lon, this.viewCenter.lat); } else { this.aladin.webglAPI.setCenter(this.viewCenter.lon, this.viewCenter.lat); } } this.forceRedraw(); this.requestRedraw(); var self = this; setTimeout(function() {self.refreshProgressiveCats();}, 1000); }; View.prototype.makeUniqLayerName = function(name) { if (! this.layerNameExists(name)) { return name; } for (var k=1;;++k) { var newName = name + '_' + k; if ( ! this.layerNameExists(newName)) { return newName; } } }; View.prototype.layerNameExists = function(name) { var c = this.allOverlayLayers; for (var k=0; k=x && s.x<=x+w && s.y>=y && s.y<=y+h) { objList.push(s); } } } } return objList; }; // update objLookup, lookup table View.prototype.updateObjectsLookup = function() { this.objLookup = []; var cat, sources, s, xRounded, yRounded; if (this.catalogs) { for (var k=0; k