UNPKG

@cquiroz/aladin-lite

Version:
1,439 lines (1,150 loc) 64.9 kB
// 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 CooFrameEnum from './CooFrameEnum'; import Circle from './Circle'; import CooConversion from './CooConversion'; import CooGrid from './CooGrid'; import Coo from './coo'; import Utils from './Utils'; import AladinUtils from './AladinUtils'; import TileBuffer from './TileBuffer'; import SpatialVector from './SpatialVector'; import { HpxImageSurvey } from './HpxImageSurvey'; import { HealpixIndex, ORDER_MAX } from './HealpixIndex'; import HealpixCache from './HealpixCache'; import HealpixGrid from './HealpixGrid'; import ProjectionEnum from './ProjectionEnum'; import SimbadPointer from './SimbadPointer'; // import Stats from 'stats.js'; import Popup from './Popup'; import { Projection } from './projection'; import Downloader from './Downloader'; import ColorMap from './ColorMap'; import Footprint from './Footprint'; import { catalog } from './A'; var View = function () { /** Constructor */ function View(aladin, location, fovDiv, cooFrame, zoom) { this.aladin = aladin; this.options = aladin.options; this.aladinDiv = this.aladin.aladinDiv; this.popup = new Popup(this.aladinDiv, this); this.createCanvases(); HealpixCache.init(); this.location = location; this.fovDiv = fovDiv; this.mustClearCatalog = true; this.mustRedrawReticle = true; this.mode = View.PAN; this.minFOV = this.maxFOV = null; // by default, no restriction this.healpixGrid = new HealpixGrid(this.imageCanvas); if (cooFrame) { this.cooFrame = cooFrame; } else { this.cooFrame = CooFrameEnum.GAL; } var lon, lat; lon = lat = 0; this.projectionMethod = ProjectionEnum.SIN; this.projection = new Projection(lon, lat); this.projection.setProjection(this.projectionMethod); this.zoomLevel = 0; this.zoomFactor = this.computeZoomFactor(this.zoomLevel); this.viewCenter = { lon: lon, lat: lat }; // position of center of view if (zoom) { this.setZoom(zoom); } // current reference image survey displayed this.imageSurvey = null; // 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 = 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.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 }; 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.addEventListener('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.clientWidth; var computedHeight = self.aladinDiv.clientHeight; if (self.width !== computedWidth || self.height === computedHeight) { self.fixLayoutDimensions(); self.setZoomLevel(self.zoomLevel); // 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 aladin = this.aladinDiv; aladin.querySelectorAll('.aladin-imageCanvas').forEach(item => item.parentNode.removeChild(item)); aladin.querySelectorAll('.aladin-catalogCanvas').forEach(item => item.parentNode.removeChild(item)); aladin.querySelectorAll('.aladin-reticleCanvas').forEach(item => item.parentNode.removeChild(item)); // a.find('.aladin-imageCanvas').remove(); // a.find('.aladin-catalogCanvas').remove(); // a.find('.aladin-reticleCanvas').remove(); // this.imageCanvas = $("<canvas class='aladin-imageCanvas'></canvas>").appendTo(this.aladinDiv)[0]; // this.catalogCanvas = $("<canvas class='aladin-catalogCanvas'></canvas>").appendTo(this.aladinDiv)[0]; // this.reticleCanvas = $("<canvas class='aladin-reticleCanvas'></canvas>").appendTo(this.aladinDiv)[0]; // canvas to draw the images this.imageCanvas = document.createElement("canvas"); this.imageCanvas.classList.add('aladin-imageCanvas'); this.aladinDiv.appendChild(this.imageCanvas); // canvas to draw the catalogs this.catalogCanvas = document.createElement("canvas"); this.catalogCanvas.classList.add('aladin-catalogCanvas'); this.aladinDiv.appendChild(this.catalogCanvas); // canvas to draw the reticle this.reticleCanvas = document.createElement("canvas"); this.reticleCanvas.classList.add('aladin-reticleCanvas'); this.aladinDiv.appendChild(this.reticleCanvas); }; // called at startup and when window is resized View.prototype.fixLayoutDimensions = function () { Utils.cssScale = undefined; // const computedWidth = self.aladinDiv.clientWidth; // const computedHeight = self.aladinDiv.clienhHeight; var computedWidth = this.aladinDiv.clientWidth; var computedHeight = this.aladinDiv.clientHeight; 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("2d"); this.catalogCtx = this.catalogCanvas.getContext("2d"); this.reticleCtx = this.reticleCanvas.getContext("2d"); this.imageCtx.canvas.width = this.width; this.catalogCtx.canvas.width = this.width; this.reticleCtx.canvas.width = this.width; this.imageCtx.canvas.height = this.height; this.catalogCtx.canvas.height = this.height; this.reticleCtx.canvas.height = this.height; pixelateCanvasContext(this.imageCtx, this.aladin.options.pixelateCanvas); // change logo if (!this.logoDiv) { this.logoDiv = this.aladinDiv.querySelectorAll('.aladin-logo')[0]; } if (this.width > 800) { this.logoDiv.classList.remove('aladin-logo-small'); this.logoDiv.classList.add('aladin-logo-large'); this.logoDiv.style.width = '90px'; } else { this.logoDiv.classList.add('aladin-logo-small'); this.logoDiv.classList.remove('aladin-logo-large'); this.logoDiv.style.width = '32px'; } this.computeNorder(); this.requestRedraw(); }; var pixelateCanvasContext = function pixelateCanvasContext(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.classList.add('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'); c.width = width || this.width; c.height = height || this.height; var ctx = c.getContext('2d'); ctx.drawImage(this.imageCanvas, 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); 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 */ View.prototype.computeFov = function (view) { var fov = this.doComputeFov(view, view.zoomFactor); view.mouseMoveIncrement = fov / view.imageCanvas.width; return fov; }; View.prototype.doComputeFov = function (view, zoomFactor) { // if zoom factor < 1, we view 180° var fov; if (view.zoomFactor < 1) { fov = 180; } 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)); } return fov; }; View.prototype.updateFovDiv = function (view) { if (view.fovDiv) { if (isNaN(view.fov)) { view.fovDiv.innerHTML = "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.innerHTML = "FoV: ".concat(fovStr); } }; View.prototype.createListeners = function (view) { var hasTouchEvents = false; if ('ontouchstart' in window) { hasTouchEvents = true; } // various listeners var onDblClick = function onDblClick(e) { var xymouse = view.imageCanvas.relMouseCoords(e); var xy = AladinUtils.viewToXy(xymouse.x, xymouse.y, view.width, view.height, view.largestDim, view.zoomFactor); try { var lonlat = view.projection.unproject(xy.x, xy.y); } catch (err) { return; } var radec = []; // convert to J2000 if needed if (view.cooFrame.system === CooFrameEnum.SYSTEMS.GAL) { radec = CooConversion.GalacticToJ2000([lonlat.ra, lonlat.dec]); } else { radec = [lonlat.ra, lonlat.dec]; } view.pointTo(radec[0], radec[1]); }; if (!hasTouchEvents) { view.reticleCanvas.addEventListener("dblclick", onDblClick); } var mouseDownEvent = function mouseDownEvent(e) { // 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]); 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)); 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 }; } return false; // to disable text selection }; view.reticleCanvas.addEventListener("mousedown", mouseDownEvent); view.reticleCanvas.addEventListener("touchstart", mouseDownEvent); //$(view.reticleCanvas).bind("mouseup mouseout touchend", function(e) { var clickEvent = function clickEvent(e) { // reacting on 'click' rather on 'mouseup' is more reliable when panning the view if (e.type === 'touchend' && view.pinchZoomParameters.isPinching) { view.pinchZoomParameters.isPinching = false; view.pinchZoomParameters.initialFov = view.pinchZoomParameters.initialDistance = undefined; 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; } } var xymouse = view.imageCanvas.relMouseCoords(e); 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.reticleCanvas.addEventListener("click", clickEvent); view.reticleCanvas.addEventListener("mouseout", clickEvent); view.reticleCanvas.addEventListener("touchend", clickEvent); var lastHoveredObject; // save last object hovered by mouse var lastMouseMovePos = null; var mouseMoveEvent = function mouseMoveEvent(e) { e.preventDefault(); if (e.type === 'touchmove' && view.pinchZoomParameters.isPinching && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length === 2) { 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; } var xymouse = view.imageCanvas.relMouseCoords(e); 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) { 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 _objHoveredFunction(null); } } } if (!hasTouchEvents) { return; } } if (!view.dragging) { return; } // var xoffset, yoffset; var pos1, pos2; if (e.originalEvent && e.originalEvent.targetTouches) { // ??? // let xoffset = e.originalEvent.targetTouches[0].clientX-view.dragx; // let 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); } else { /* xoffset = e.clientX-view.dragx; yoffset = e.clientY-view.dragy; */ // let xoffset = xymouse.x-view.dragx; // let yoffset = xymouse.y-view.dragy; var _xy = 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(_xy.x, _xy.y); pos2 = view.projection.unproject(_xy2.x, _xy2.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; // 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; view.requestRedraw(); }; //// endof mousemove //// view.reticleCanvas.addEventListener("mousemove", mouseMoveEvent); view.reticleCanvas.addEventListener("touchmove", mouseMoveEvent); // disable text selection on IE view.aladinDiv.onselectstart = function () { return false; }; view.reticleCanvas.onwheel = function (event) { event.preventDefault(); event.stopPropagation(); 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 (Object.prototype.hasOwnProperty.call(event, 'originalEvent')) { delta = -event.originalEvent.deltaY; } if (delta > 0) { level += 1; } else { level -= 1; } view.setZoomLevel(level); return false; }; }; var init = function init(view) { // var stats = new Stats(); // stats.domElement.style.top = '50px'; // document.querySelectorAll('#aladin-statsDiv').forEach((item) => item.appendChild(stats.domElement)); // if ($('#aladin-statsDiv').length>0) { // $('#aladin-statsDiv')[0].appendChild( stats.domElement ); // } // view.stats = stats; view.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 = view.computeFov(view); 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.projection.unproject(xy.x, xy.y); } catch (err) { console.log(err); } if (lonlat) { view.location.update(lonlat.ra, lonlat.dec, 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; window.requestAnimationFrame(this.redraw.bind(this)); var now = new Date().getTime(); if (this.dateRequestDraw && now > this.dateRequestDraw) { this.dateRequestDraw = null; } else if (!this.needRedraw) { if (!this.flagForceRedraw) { return; } else { this.flagForceRedraw = false; } } // this.stats.update(); 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) ***************** // let 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); } } if (cornersXYViewMapHighres && this.curNorder > 3) { this.healpixGrid.redraw(imageCtx, cornersXYViewMapHighres, this.fov, this.curNorder); } else { this.healpixGrid.redraw(imageCtx, cornersXYViewMapAllsky, this.fov, 3); } } // redraw coordinates grid if (this.showGrid) { if (this.cooGrid == null) { this.cooGrid = new CooGrid(); } this.cooGrid.redraw(imageCtx, 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; i < this.catalogs.length; i++) { var cat = this.catalogs[i]; cat.draw(catalogCtx, this.projection, this.cooFrame, this.width, this.height, this.largestDim, this.zoomFactor); } } // draw popup catalog if (this.catalogForPopup.isShowing && this.catalogForPopup.sources.length > 0) { 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; _i < this.overlays.length; _i++) { this.overlays[_i].draw(overlayCtx, this.projection, this.cooFrame, this.width, this.height, this.largestDim, this.zoomFactor); } } // draw MOCs var mocCtx = this.catalogCtx; if (this.mocs && this.mocs.length > 0 && (!this.dragging || View.DRAW_MOCS_WHILE_DRAGGING)) { if (!catalogCanvasCleared) { catalogCtx.clearRect(0, 0, this.width, this.height); catalogCanvasCleared = true; } for (var _i2 = 0; _i2 < this.mocs.length; _i2++) { this.mocs[_i2].draw(mocCtx, this.projection, this.cooFrame, this.width, this.height, this.largestDim, this.zoomFactor, this.fov); } } // if (this.mode===View.SELECT) { // var mustRedrawReticle = true; // } ////// 4. Draw reticle /////// // TODO: reticle should be placed in a static DIV, no need to waste a canvas var reticleCtx = this.reticleCtx; if (this.mustRedrawReticle || this.mode === View.SELECT) { reticleCtx.clearRect(0, 0, this.width, this.height); } if (this.displayReticle) { if (!this.reticleCache) { // build reticle image var c = document.createElement('canvas'); var s = this.options.reticleSize; c.width = s; c.height = s; var ctx = c.getContext('2d'); ctx.lineWidth = 2; ctx.strokeStyle = this.options.reticleColor; ctx.beginPath(); ctx.moveTo(s / 2, s / 2 + (s / 2 - 1)); ctx.lineTo(s / 2, s / 2 + 2); ctx.moveTo(s / 2, s / 2 - (s / 2 - 1)); ctx.lineTo(s / 2, s / 2 - 2); ctx.moveTo(s / 2 + (s / 2 - 1), s / 2); ctx.lineTo(s / 2 + 2, s / 2); ctx.moveTo(s / 2 - (s / 2 - 1), s / 2); ctx.lineTo(s / 2 - 2, s / 2); ctx.stroke(); this.reticleCache = c; } reticleCtx.drawImage(this.reticleCache, this.width / 2 - this.reticleCache.width / 2, this.height / 2 - this.reticleCache.height / 2); this.mustRedrawReticle = false; } ////// 5. Draw all-sky ring ///// if (this.projectionMethod === ProjectionEnum.SIN && this.fov >= 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(); }; View.prototype.forceRedraw = function () { this.flagForceRedraw = true; }; View.prototype.refreshProgressiveCats = function () { if (!this.catalogs) { return; } for (var i = 0; i < this.catalogs.length; i++) { if (this.catalogs[i].type === 'progressivecat') { this.catalogs[i].loadNeededTiles(); } } }; View.prototype.getVisiblePixList = function (norder, frameSurvey) { var nside = Math.pow(2, norder); var pixList; var npix = HealpixIndex.nside2Npix(nside); if (this.fov > 80) { pixList = []; for (var ipix = 0; ipix < npix; ipix++) { pixList.push(ipix); } } else { var hpxIdx = new HealpixIndex(nside); hpxIdx.init(); var spatialVector = new SpatialVector(); // if frame != frame image survey, we need to convert to survey frame system var xy = AladinUtils.viewToXy(this.cx, this.cy, this.width, this.height, this.largestDim, this.zoomFactor); var radec = this.projection.unproject(xy.x, xy.y); var lonlat = []; if (frameSurvey && frameSurvey.system !== this.cooFrame.system) { if (frameSurvey.system === CooFrameEnum.SYSTEMS.J2000) { lonlat = CooConversion.GalacticToJ2000([radec.ra, radec.dec]); } else if (frameSurvey.system === CooFrameEnum.SYSTEMS.GAL) { lonlat = CooConversion.J2000ToGalactic([radec.ra, radec.dec]); } } else { lonlat = [radec.ra, radec.dec]; } if (this.imageSurvey && this.imageSurvey.longitudeReversed === true) { spatialVector.set(lonlat[0], lonlat[1]); } else { spatialVector.set(lonlat[0], lonlat[1]); } var radius = this.fov * 0.5 * this.ratio; // we need to extend the radius if (this.fov > 60) { 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 = Utils.radecToPolar(lonlat[0], lonlat[1]); var ipixCenter = hpxIdx.ang2pix_nest(polar.theta, polar.phi); pixList.unshift(ipixCenter); } return pixList; }; // 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; _ipix < npix; _ipix++) { pixList.push(_ipix); } } else { var hpxIdx = new HealpixIndex(nside); var spatialVector = new SpatialVector(); // if frame != frame image survey, we need to convert to survey frame system var xy = AladinUtils.viewToXy(this.cx, this.cy, this.width, this.height, this.largestDim, this.zoomFactor); var radec = this.projection.unproject(xy.x, xy.y); var lonlat = []; if (frameSurvey && frameSurvey.system !== this.cooFrame.system) { if (frameSurvey.system === CooFrameEnum.SYSTEMS.J2000) { lonlat = CooConversion.GalacticToJ2000([radec.ra, radec.dec]); } else if (frameSurvey.system === CooFrameEnum.SYSTEMS.GAL) { lonlat = CooConversion.J2000ToGalactic([radec.ra, radec.dec]); } } else { lonlat = [radec.ra, radec.dec]; } if (this.imageSurvey && this.imageSurvey.longitudeReversed === true) { spatialVector.set(lonlat[0], lonlat[1]); } else { spatialVector.set(lonlat[0], lonlat[1]); } var radius = this.fov * 0.5 * this.ratio; // we need to extend the radius if (this.fov > 60) { 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 = Utils.radecToPolar(lonlat[0], lonlat[1]); ipixCenter = hpxIdx.ang2pix_nest(polar.theta, polar.phi); pixList.unshift(ipixCenter); } var ipix; var lon, lat; for (var ipixIdx = 0, len = pixList.length; ipixIdx < len; ipixIdx++) { ipix = pixList[ipixIdx]; if (ipix === ipixCenter && ipixIdx > 0) { continue; } var cornersXYView = []; var corners = HealpixCache.corners_nest(ipix, nside); for (var k = 0; k < 4; k++) { if (corners) 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 _radec2 = CooConversion.GalacticToJ2000([spVec.ra(), spVec.dec()]); lon = _radec2[0]; lat = _radec2[1]; } } else { lon = spVec.ra(); lat = spVec.dec(); } cornersXY[k] = this.projection.project(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); } // 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 AITOFF --> TO BE MODIFIED if (this.projection.PROJECTION === ProjectionEnum.AITOFF) { 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; } } 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 _radec3 = CooConversion.GalacticToJ2000([spVec.ra(), spVec.dec()]); lon = _radec3[0]; lat = _radec3[1]; } } else { lon = spVec.ra(); lat = spVec.dec(); } cornersXY[k] = this.projection.project(lon, lat); } if (cornersXY[0] == null || cornersXY[1] == null || cornersXY[2] == null || cornersXY[3] == null) { return null; } for (var _k2 = 0; _k2 < 4; _k2++) { cornersXYView[_k2] = AladinUtils.xyToView(cornersXY[_k2].X, cornersXY[_k2].Y, this.width, this.height, this.largestDim, this.zoomFactor); } return cornersXYView; }; View.prototype.computeZoomFactor = function (level) { if (level > 0) { return AladinUtils.getZoomFactorForAngle(180 / Math.pow(1.15, level), this.projectionMethod); } else { return 1 + 0.1 * level; } }; View.prototype.setZoom = function (fovDegrees) { if (fovDegrees < 0 ||