Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PolySelect implementation and miscellaneous selection improvements #195

Merged
merged 12 commits into from
Nov 8, 2024
1 change: 1 addition & 0 deletions src/css/aladin.css
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ canvas {
font-family: monospace;
line-height: 1rem;

/*z-index: 100;*/

float: left;
}
Expand Down
6 changes: 6 additions & 0 deletions src/js/Aladin.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ import { Polyline } from "./shapes/Polyline";
* @property {boolean} [samp=false] - Whether to enable SAMP (Simple Application Messaging Protocol).
* @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode.
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
* @property {boolean} [manualSelection=false] - When set to true, no selection will be performed, only events will be generated.
* @property {Object} [selector] - More options for the the selector.
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
* @property {number} [selector.lineWidth=2] - Width of the selector line.
*
* @example
* let aladin = A.aladin({
target: 'galactic center',
Expand Down Expand Up @@ -712,6 +717,7 @@ export let Aladin = (function () {
samp: false,
realFullscreen: false,
pixelateCanvas: true,
manualSelection: false
};

// realFullscreen: AL div expands not only to the size of its parent, but takes the whole available screen estate
Expand Down
6 changes: 6 additions & 0 deletions src/js/DefaultActionsForContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ export let DefaultActionsForContextMenu = (function () {
action(o) {
a.select('rect', selectObjects)
}
},
{
label: 'Polygon',
action(o) {
a.select('poly', selectObjects)
}
}
]
},
Expand Down
7 changes: 4 additions & 3 deletions src/js/FiniteStateMachine/CircleSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ export class CircleSelect extends FSM {
let ctx = view.catalogCtx;

// draw the selection
ctx.fillStyle = options.color + '7f';
ctx.strokeStyle = options.color;
let colorValue = (typeof options.color === 'function') ? options.color(this.startCoo, this.coo) : options.color;
ctx.fillStyle = colorValue + '7f';
ctx.strokeStyle = colorValue;
ctx.lineWidth = options.lineWidth;

var r2 = (this.coo.x - this.startCoo.x) * (this.coo.x - this.startCoo.x) + (this.coo.y - this.startCoo.y) * (this.coo.y - this.startCoo.y);
Expand Down Expand Up @@ -112,7 +113,7 @@ export class CircleSelect extends FSM {
}

// execute selection callback only
(typeof this.callback === 'function') && this.callback(s);
(typeof this.callback === 'function') && this.callback(s, Selector.getObjects(s, view));

this.dispatch("off");
};
Expand Down
34 changes: 27 additions & 7 deletions src/js/FiniteStateMachine/PolySelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ActionButton } from "../gui/Widgets/ActionButton";
import { View } from "../View";
import finishIconUrl from '../../../assets/icons/finish.svg';
import { Utils } from "../Utils";
import { Selector } from "../Selector";

/******************************************************************************
* Aladin Lite project
Expand Down Expand Up @@ -70,7 +71,6 @@ export class PolySelect extends FSM {
this.callback = callback;
// reset the coo
this.coos = [];

}

let click = (params) => {
Expand Down Expand Up @@ -98,7 +98,6 @@ export class PolySelect extends FSM {
e.stopPropagation();
e.preventDefault()

btn.remove();
self.dispatch('finish');
}
});
Expand All @@ -120,11 +119,12 @@ export class PolySelect extends FSM {

let draw = () => {
let ctx = view.catalogCtx;

// draw the selection
ctx.save();
ctx.fillStyle = options.color + '7f';
ctx.strokeStyle = options.color;
let colorValue = (typeof options.color === 'function') ? options.color() : options.color;
ctx.fillStyle = colorValue + '7f';
ctx.strokeStyle = colorValue;
ctx.lineWidth = options.lineWidth;

ctx.beginPath();
Expand All @@ -145,6 +145,10 @@ export class PolySelect extends FSM {
}

let finish = () => {
if(btn) {
btn.remove();
}

if (this.coos.length <= 2) {
console.warn("Invalid selection, please draw at least a 3 vertices polygon")

Expand Down Expand Up @@ -174,17 +178,32 @@ export class PolySelect extends FSM {
let s = {
vertices: this.coos,
label: 'polygon',
contains(s) {
let x = s.x;
let y = s.y;

let inside = false;
for (let i = 0, j = this.vertices.length - 1; i < this.vertices.length; j = i++) {
let xi = this.vertices[i].x, yi = this.vertices[i].y;
let xj = this.vertices[j].x, yj = this.vertices[j].y;

let intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
},
bbox() {
return {x, y, w, h}
}
};
(typeof this.callback === 'function') && this.callback(s);
(typeof this.callback === 'function') && this.callback(s, Selector.getObjects(s, view));

// execute general callback
if (view.aladin.callbacksByEventName) {
var callback = view.aladin.callbacksByEventName['objectsSelected'] || view.aladin.callbacksByEventName['select'];
if (typeof callback === "function") {
console.warn('polygon selection is not fully implemented, PolySelect.contains is needed for finding sources inside a polygon')
let objList = Selector.getObjects(s, view);
callback(objList);
}
}

Expand Down Expand Up @@ -257,6 +276,7 @@ export class PolySelect extends FSM {
//mouseout,
mousemove,
draw,
finish,
},
mouseout: {
start,
Expand Down
8 changes: 5 additions & 3 deletions src/js/FiniteStateMachine/RectSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ export class RectSelect extends FSM {
let ctx = view.catalogCtx;

// draw the selection
ctx.fillStyle = options.color + '7f';
ctx.strokeStyle = options.color;
let colorValue = (typeof options.color === 'function') ? options.color(this.startCoo, this.coo) : options.color;

ctx.fillStyle = colorValue + '7f';
ctx.strokeStyle = colorValue;
ctx.lineWidth = options.lineWidth;

var w = this.coo.x - this.startCoo.x;
Expand Down Expand Up @@ -99,7 +101,7 @@ export class RectSelect extends FSM {
}
};

(typeof this.callback === 'function') && this.callback(s);
(typeof this.callback === 'function') && this.callback(s, Selector.getObjects(s, view));

// TODO: remove these modes in the future
view.aladin.showReticle(true)
Expand Down
31 changes: 26 additions & 5 deletions src/js/Selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Selector {

var objList = [];
var cat, sources, s;
var footprints, f;
var overlayItems, f;
var objListPerCatalog = [];
if (view.catalogs) {
for (var k = 0; k < view.catalogs.length; k++) {
Expand All @@ -130,11 +130,11 @@ export class Selector {
}
}
// footprints
footprints = cat.getFootprints();
if (footprints) {
overlayItems = cat.getFootprints();
if (overlayItems) {
const {x, y, w, h} = selection.bbox();
for (var l = 0; l < footprints.length; l++) {
f = footprints[l];
for (var l = 0; l < overlayItems.length; l++) {
f = overlayItems[l];
if (f.intersectsBBox(x, y, w, h, view)) {
objListPerCatalog.push(f);
}
Expand All @@ -148,6 +148,27 @@ export class Selector {
}
}

if (view.overlays) {
const {x, y, w, h} = selection.bbox();
for (var k = 0; k < view.overlays.length; k++) {
let overlay = view.overlays[k];
if (!overlay.isShowing) {
continue;
}
var overlayItems = overlay.overlayItems;
for (var l = 0; l < overlayItems.length; l++) {
let o = overlayItems[l];
if (!o.isShowing) {
continue;
}

if (o.intersectsBBox(x, y, w, h, view)) {
objList.push([o]);
}
}
}
}

return objList;
}
}
20 changes: 15 additions & 5 deletions src/js/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ export let View = (function () {
const cooFrame = CooFrameEnum.fromString(this.options.cooFrame, CooFrameEnum.J2000);
this.changeFrame(cooFrame);

this.selector = new Selector(this);
this.selector = new Selector(this, this.options.selector);
this.manualSelection = (this.options && this.options.manualSelection) || false;

// current reference image survey displayed
this.imageLayers = new Map();
Expand Down Expand Up @@ -577,7 +578,7 @@ export let View = (function () {
const xymouse = Utils.relMouseCoords(e);

// deselect all the selected sources with Select panel
view.unselectObjects()
view.unselectObjects();

try {
const [lon, lat] = view.aladin.pix2world(xymouse.x, xymouse.y, 'icrs');
Expand Down Expand Up @@ -620,7 +621,7 @@ export let View = (function () {
var handleSelect = function(xy, tolerance) {
tolerance = tolerance || 5;
var objs = view.closestObjects(xy.x, xy.y, tolerance);
// Deselect objects if any

view.unselectObjects();

if (objs) {
Expand All @@ -642,7 +643,7 @@ export let View = (function () {
(typeof objClickedFunction === 'function') && objClickedFunction(o, xy);

if (o.isFootprint()) {
if (typeof footprintClickedFunction === 'function' && (!view.lastClickedObject || !view.lastClickedObject.includes(o))) {
if (typeof footprintClickedFunction === 'function') {
footprintClickedFunction(o, xy);
}
}
Expand All @@ -652,6 +653,7 @@ export let View = (function () {
objs = Array.from(Object.values(objsByCats));
view.selectObjects(objs);
view.lastClickedObject = objs;

} else {
// If there is a past clicked object
if (view.lastClickedObject) {
Expand Down Expand Up @@ -1448,6 +1450,10 @@ export let View = (function () {
};

View.prototype.unselectObjects = function() {
if (this.manualSelection) {
return;
}

this.aladin.measurementTable.hide();

if (this.selection) {
Expand All @@ -1467,9 +1473,13 @@ export let View = (function () {
}

View.prototype.selectObjects = function(selection) {
if (this.manualSelection) {
return;
}

// unselect the previous selection
this.unselectObjects();

if (Array.isArray(selection)) {
this.selection = selection;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/js/shapes/Circle.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ export let Circle = (function() {
// From StackOverflow: https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
Circle.prototype.intersectsBBox = function(x, y, w, h) {
const circleDistance = {
x: abs(this.center.x - x),
y: abs(this.center.y - y)
x: Math.abs(this.center.x - x),
y: Math.abs(this.center.y - y)
};

if (circleDistance.x > (w/2 + this.radius)) { return false; }
Expand Down
47 changes: 45 additions & 2 deletions src/js/shapes/Polyline.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,51 @@ export let Polyline = (function() {
return false;
};

Polyline.prototype.intersectsBBox = function(x, y, w, h) {
// todo
Polyline.prototype.intersectsBBox = function(x, y, w, h, view) {
for (let i = 0; i < this.raDecArray.length - 1; i++) {
let p1 = this.raDecArray[i];
let p2 = this.raDecArray[i + 1];

let xy1 = view.aladin.world2pix(p1[0], p1[1]);
let xy2 = view.aladin.world2pix(p2[0], p2[1]);

if (!xy1 || !xy2) {
return false;
}

xy1 = {x: xy1[0], y: xy1[1]};
xy2 = {x: xy2[0], y: xy2[1]};

// Check if line segment intersects with the bounding box
if (this.lineIntersectsBox(xy1, xy2, x, y, w, h)) {
return true;
}
}

return false;
};

Polyline.prototype.lineIntersectsBox = function(p1, p2, x, y, w, h) {
// Check if line segment is completely outside the box
if ((p1.x < x && p2.x < x) ||
(p1.y < y && p2.y < y) ||
(p1.x > x + w && p2.x > x + w) ||
(p1.y > y + h && p2.y > y + h)) {
return false;
}

let m = (p2.y - p1.y) / (p2.x - p1.x); // Slope of the line
let c = p1.y - m * p1.x; // y-intercept of the line

// Check if line intersects with the sides of the box
if ((p1.y >= y && p1.y <= y + h) ||
(p2.y >= y && p2.y <= y + h) ||
(m * x + c >= y && m * x + c <= y + h) ||
(m * (x + w) + c >= y && m * (x + w) + c <= y + h)) {
return true;
}

return false;
};

// static methods
Expand Down
Loading