import {isString, isNumber, isNil, isObject, isArray} from 'lodash';
import CoordinateSys from './CoordSys.js';
import Resolver, {parseResolver} from '../astro/net/Resolver.js';
import validator from 'validator';
const SPT= 'ScreenPt';
const IM_PT= 'ImagePt';
const FITS_IM_PT= 'FitsImagePt';
const ZERO_BASED_IM_PT= 'ZeroBasedImagePt';
const IM_WS_PT= 'ImageWorkSpacePt';
const W_PT= 'WorldPt';
const DEV_PT= 'DevicePt';
const PROJ_PT= 'ProjectionPt';
const OFFSET_PT= 'OffsetPt';
const Point = { SPT, IM_PT, IM_WS_PT, FITS_IM_PT, ZERO_BASED_IM_PT, DEV_PT, PROJ_PT, W_PT, OFFSET_PT};
/**
* @typedef {Object} Point
* @summary a Point
*
* @prop {Number} x
* @prop {Number} y
* @prop {String} type one of 'RenderedPt', 'ScreenPt', 'ImagePt', 'ImageWorkSpacePt', 'WorldPt', 'ProjectionPt', 'OffsetPt'
* @public
* @global
*/
/**
* @typedef {Object} DevicePt
* @summary a rendered point on the device screen, including rotation, flipping, etc
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'DevicePt'
* @public
* @global
*/
/**
* @typedef {Object} ScreenPt
* @summary a point on the image screen
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'ScreenPt'
* @public
* @global
*/
/**
* @typedef {Object} ImagePt
* @summary a point in image file coordinates
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'ImagePt'
* @public
* @global
*/
/**
* @typedef {Object} FitsImagePt
* @summary a point in FITS standard image file coordinates
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'ImagePt'
* @public
* @global
*/
/**
* @typedef {Object} ZeroBasedImagePt
* @summary a point in Zero based image file coordinates, ca be offset by LTV1 and LTV2
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'ImagePt'
* @public
* @global
*/
/**
* @typedef {Object} WorldPt
* @summary a point on the sky
* @prop {Number} x
* @prop {Number} y
* @prop {String} type constant must be 'WorldPt'
* @prop {CoordinateSys} cSys - the coordinate system constant
* @prop {String} objName - the object name the was used for name resolution, may not be defined
* @prop {Resolver} objName - the resolver used to create this worldPt, may not be defined
*
* @public
* @global
*/
export class SimplePt {
constructor(objOrX,y, forceToInt) {
if (isArray(objOrX)) {
forceToInt= y || forceToInt; // in this case, forceToInt was passed in second position
this.x= Number(objOrX[0]);
this.y= Number(objOrX[1]);
}
else if (isObject(objOrX)) {
forceToInt= y || forceToInt; // in this case, forceToInt was passed in second position
this.x= Number(objOrX.x);
this.y= Number(objOrX.y);
}
else {
this.x= Number(objOrX);
this.y= Number(y);
}
if (forceToInt) {
this.x= Math.trunc(this.x);
this.y= Math.trunc(this.y);
}
}
toString() { return this.x+';'+this.y; }
static make(x, y, type) {
const pt = new SimplePt(x, y);
if (type && Object.keys(Point).includes(type)) {
pt.type = type;
}
return pt;
}
}
/**
* WorldPt constructor
* @type {Function}
* @constructor
* @alias module:firefly/visualize/Pt.WorldPt
* @param {number} lon - the longitude
* @param {number} lat - the latitude
* @param {CoordinateSys} [coordSys=CoordinateSys.EQ_J2000]- the coordinate system constant
* @param {string} [objName] - the object name the was used for name resolution
* @param {Resolver} [resolver] - the resolver use to return this point
* @public
* @global
*/
export class WorldPt {
constructor(lon,lat,coordSys,objName,resolver) {
this.x= lon;
this.y= lat;
this.cSys = coordSys || CoordinateSys.EQ_J2000;
if (objName) {
this.objName = objName;
}
if (resolver) {
this.resolver = resolver;
}
this.type= W_PT;
}
/**
* Return the lon
* @type {function(this:exports.WorldPt)}
* @return {Number}
*/
getLon() { return this.x; }
/**
* Return the lat
* @type {function(this:exports.WorldPt)}
* @return {Number}
*/
getLat() { return this.y; }
/**
* Returns the coordinate system of this point
* @type {function(this:exports.WorldPt)}
* @returns {CoordinateSys}
*/
getCoordSys() { return this.cSys; }
getResolver() { return this.resolver ? this.resolver : null; }
getObjName() { return (this.objName) ? this.objName : null; }
/**
* return the string representation of the WorldPt. This output can be used
* to recreate a WorldPt using parseWorldPt
* @type {function(this:exports.WorldPt)}
* @return {string}
*/
toString() {
let retval = this.x + ';' + this.y + ';' + this.cSys.toString();
if (this.objName) {
retval += ';' + this.objName;
if (this.resolver) {
retval += ';' + this.resolver.key;
}
}
return retval;
}
static parse(inStr) {
return parseWorldPt(inStr);
}
}
/**
*
* @param {Array.<String>} wpParts
* @return {*}
*/
function stringAryToWorldPt(wpParts) {
const len= wpParts.length;
const x= Number(wpParts[0]);
const y= Number(wpParts[1]);
if (isNaN(x) || isNaN(y)) return null;
const csys= CoordinateSys.parse(wpParts[2]);
if (csys===CoordinateSys.PIXEL) return makeImagePt(x, y); // image point
if (csys===CoordinateSys.SCREEN_PIXEL) return makeScreenPt(x, y); // screen point
if (len===2 || len===3) return makeWorldPt(x,y,csys);
const resolver= wpParts[4] ? parseResolver(wpParts[4]) : Resolver.UNKNOWN;
return makeWorldPt(x,y,csys, wpParts[3], resolver);
}
/**
* @summary A point on the sky with a coordinate system
* @param {number|string} lon - longitude in degrees, strings are converted to numbers
* @param {number|string} lat - latitude in degrees, strings are converted to numbers
* @param {CoordinateSys} [coordSys] - The coordinate system of this worldPt
*
* @param {String} [objName] - object name used to create this worldPt
* @param [resolver] - the resolver used to create this worldPt
* @return {WorldPt}
*
*
* @function makeWorldPt
* @public
* @global
*/
export function makeWorldPt(lon,lat,coordSys= undefined,objName= undefined,resolver= undefined) {
return new WorldPt(Number(lon),Number(lat),coordSys,objName,resolver) ;
}
/**
* @summary A point in the image file
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
*
* @return {ImagePt}
*
* @function makeImagePt
* @memberof firefly.util.image
* @public
* @global
*/
export const makeImagePt= (x,y) => Object.assign(new SimplePt(x,y), {type:IM_PT});
/**
* @summary A image point in a FITS file with FITS based offset Standard
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
*
* @return {FitsImagePt}
*
* @function makeFitsImagePt
* @memberof firefly.util.image
* @public
* @global
*/
export const makeFitsImagePt= (x,y) => Object.assign(new SimplePt(x,y), {type:FITS_IM_PT});
/**
* @summary A image point in a Zero based standard, can be offset with the LVT1 and LTV2 headers
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
*
* @return {ImagePt}
*
* @function makeZeroBasedImagePt
* @memberof firefly.util.image
* @public
* @global
*/
export const makeZeroBasedImagePt= (x,y) => Object.assign(new SimplePt(x,y), {type:ZERO_BASED_IM_PT});
/**
*
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
* @memberof firefly.util.image
* @return {Pt}
*/
export const makeImageWorkSpacePt= (x,y) => Object.assign(new SimplePt(x,y), {type:IM_WS_PT});
/**
* @summary A point of the display image
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
*
* @return {ScreenPt}
*
* @function makeScreenPt
* @memberof firefly.util.image
* @public
* @global
*/
export const makeScreenPt= (x,y) => Object.assign(new SimplePt(x,y), {type:SPT});
/**
* @summary A point on the physical space of the image display area
* @param {number|string} x - the x, string is converted to number
* @param {number|string} y - the y, string is converted to number
*
* @return {DevicePt}
*
* @function makeDevicePt
* @memberof firefly.util.image
* @public
* @global
*/
export const makeDevicePt= (x,y) => Object.assign(new SimplePt(x,y), {type:DEV_PT});
export const makeProjectionPt= (x,y) => Object.assign(new SimplePt(x,y), {type:PROJ_PT});
export const makeOffsetPt= (x,y) => Object.assign(new SimplePt(x,y), {type:OFFSET_PT});
/**
* given an x,y, and a CoordinageSys object or string, make the correct type of point.
* @param {number} x
* @param {number} y
* @param {CoordinateSys|String} coordSys
* @return {Point}
*/
export function makeAnyPt(x,y,coordSys) {
const csys= (coordSys.isEquatorial && coordSys.getJsys && coordSys.getEquinox) ?
coordSys : CoordinateSys.parse(coordSys);
switch (csys) {
case CoordinateSys.SCREEN_PIXEL:
case CoordinateSys.UNDEFINED: return makeScreenPt(x, y);
case CoordinateSys.PIXEL: return makeImagePt(x, y);
case CoordinateSys.ZEROBASED: return makeZeroBasedImagePt(x, y);
case CoordinateSys.FITSPIXEL: return makeFitsImagePt(x, y);
default: return makeWorldPt(x,y,coordSys);
}
}
/**
* @summary Test if two points are equals. They must be the same coordinate system and have the same values to be
* equal. Two points that are null or undefined are also considered equal.
* If both points are WorldPt and are equal in values and coordinate system but have a
* different resolver and object names, * they are still considered equal.
*
* @param {Point} p1 - the first point
* @param {Point} p2 - the second point
*
* @return true if equals
*
* @function pointEquals
* @memberof firefly.util.image
* @public
* @global
*/
export function pointEquals(p1,p2) {
if (isNil(p1) && isNil(p2)) return true;
else if (isNil(p1) || isNil(p2)) return false;
return (p1.x===p2.x && p1.y===p2.y && p1.type===p2.type && p1.csys===p2.csys);
}
/**
* @summary Parse a point
* @param type
* @param inStr
* @return {Point}
* @memberof firefly.util.image
* @public
* @global
*/
export const parsePt= function(type, inStr) {
if (!inStr) return null;
const parts= inStr.split(';');
if (parts.length===2 && validator.isFloat(parts[0]) && validator.isFloat(parts[1])) {
const pt= new SimplePt(validator.toFloat(parts[0]), validator.toFloat(parts[1]));
pt.type= type;
return pt;
}
return null;
};
export const parseImagePt = (inStr) => parsePt(IM_PT,inStr);
export const parseImageWorkSpacePt = (inStr) => parsePt(IM_WS_PT,inStr);
export const parseScreenPt= (inStr) => parsePt(SPT,inStr);
/**
*
* @param serializedWP
* @return {WorldPt}
*/
export const parseWorldPt = function (serializedWP) {
if (!serializedWP || !isString(serializedWP)) return null;
const sAry= serializedWP.split(';');
if (sAry.length<2 || sAry.length>5) return null;
return stringAryToWorldPt(sAry);
};
const ptTypes= Object.values(Point);
/**
* Make sure this is a defined value, valid point object, has a valid type, and the coordinates are numbers.
* @param {*} testPt - any value
* @return {boolean} true if this point is valid
*/
export const isValidPoint= (testPt) => Boolean(testPt &&
testPt.type && ptTypes.includes(testPt.type) &&
isNumber(testPt.x) && isNumber(testPt.y));
export default Point;