//mz_vis_obj_lists.ssc

//Select and copy the text below the line, paste it into the Stellarium script console (toggle console with [F12] key), make sure there is a line break after the last line, save as "mz_vis_obj_lists.ssc" with file type "Stellarium Script (*.ssc)".
//________________________________________

// Name: mz_vis_obj_lists
// Author: Magnus Zeisig
// License: Public Domain
// Version: 0.2
// Date: 2025-10-10 - 2025-12-01
// Description: This script marks deep sky objects
//              from e.g. the (M, C,) NGC and IC catalogs
//              (set "use" to true/false in "cats" below).
//              Objects with a diameter of e.g. 1-11',
//              brighter than magnitude e.g. 12,
//              with altitude that can be above e.g. 15°
//              at observer location latitude
//              when atmosphere is off, or
//              with altitude that is above e.g. 15°
//              at observer location and time
//              when atmosphere is on.
//              Magnitude is visual or blue (some galaxies).
//              Magnitude is adjusted for atmospheric
//              extinction when atmosphere is on.
//              Marker colors are
//              red for galactic objects,
//              yellow for star clusters, and
//              green for nebulae.
//              blue for dark nebulae.
//              Marker colors are saturated
//              100 % for (e.g.) magnitude 0 - 9
//              80 % for (e.g.) magnitude 9 - 10
//              60 % for (e.g.) magnitude 10 - 11
//              40 % for (e.g.) magnitude 11 - 12
//              Script needs to be run after setting
//              observer location, time and atmosphere.
//              Script logs info and data compilations.
//              Run takes ~50 s on MacBook Pro 16" 2021.
//              Tested with Stellarium v25.2 script engine.
//

include("mz_vis_obj_ids.inc");
'use strict';

function hex2rgb(hex) {
    return [
        Number('0x' + hex.substring(1, 3)),
        Number('0x' + hex.substring(3, 5)),
        Number('0x' + hex.substring(5, 7))
    ];
}
function c2hex(c) {
    return c >= 256 ? 'ff' : (c < 16 ? '0' : '') + Math.floor(c).toString(16);
}
function rgb2hex(r, g, b) {
    return '#' + c2hex(r) + c2hex(g) + c2hex(b);
}

var focalLength, focalReduction, sensorMinWidth, sensorMargin, sizeLo, sizeHi, magLo, magHi, altLo, aec, diam, cats, rgbs, lat, decLo, decHi, atm, aec1, obsKey, ids, cHi, c, idLists, n, txts, cat, iHi, i, id, info, des, rx, matches, size, vmag, mag, band, vmage, mage, type, dec, alt, r_a, azi, k, rgbHex, rgb, txt;

focalLength = 2800; // STAR's Celestron CPC 1100 GPS (XLT) telescope (mm)
focalReduction = 0.63; // 0.63 with and 1.00 without Celestron Flattener/Reducer f/6.3
// sensorMinWidth = 14.9; // camera Canon EOS 90D (mm)
sensorMinWidth = 6.3; // camera ZWO ASI 585 MC-Air (mm)
sensorMargin = 5.0; // margin, sensor edge to object of sensor min dimension, (%)
sizeLo = 1 / 60; // object min diameter (°) 1'
sizeHi = 180.0 / Math.PI * 2.0 * Math.atan(sensorMinWidth / 2.0 / focalLength / focalReduction) * (1.0 - 0.02 * sensorMargin); // object max diameter (°) Canon: 26.1', ZWO: 11.0'
magLo = 0.0; // object min V or B magnituder
magHi = 12.0; // object max V or B magnituder
altLo = 15.0; // object min altitude angle (°)
aec = 0.28; // atmospheric extinction coefficient per air masses, default 0.13
diam = 12.0; // marker diameter (px)
cats = [ // deep sky object catalogs to render
    {name: 'Messier', short: 'M', count: 110, use: false},
    {name: 'Caldwell', short: 'C', count: 109, use: false},
    {name: 'New General Catalog', short: 'NGC', count: 7839, use: true},
    {name: 'Index Catalogs', short: 'IC', count: 5386, use: true},
    {name: 'Barnard Catalog', short: 'B', count: 370, use: true},
    {name: 'Lynds\' Catalog of Dark Nebulae', short: 'LDN', count: 1802, use: false},
    {name: '', short: '', count: 0, use: false} // leave this dummy as last catalog
]; // set parameter "use" to true to include and false to exclude when rendering
rgbs = { // marker colors
    'galaxy': '#ff0000', // M&C: 52/89, NGC: 219/566
    'active galaxy': '#ff0000', // M&C: 0/89, NGC: 111/566
    'interacting galaxy': '#ff0000', // M&C: 0/89, NGC: 45/566
    'radio galaxy': '#ff0000', // M&C: 0/89, NGC: 4/566
    'quasar': '#ff0000', // M&C: 0/89, NGC: 1/566
    'blazar': '#ff0000', // M&C: 0/89, NGC: 1/566
    'open star cluster': '#ffff00', // M&C: 14/89, NGC: 118/566
    'globular star cluster': '#ffff00', // M&C: 12/89, NGC: 23/566
    'star cluster': '#ffff00', // M&C: 0/89, NGC: 2/566
    'stellar association': '#ffff00', // M&C: 0/89, NGC: 1/566
    'cluster associated with nebulosity': '#ffff00', // M&C: 2/89, NGC: 6/566
    'reflection nebula': '#00ff00', // M&C: 2/89, NGC: 17/566
    'planetary nebula': '#00ff00', // M&C: 5/89, NGC: 10/566
    'reflection nebula': '#00ff00', // M&C: 2/89, NGC: 18/566
    'bipolar nebula': '#00ff00', // M&C: 0/89, NGC: 1/566
    'dark nebula': '#0000ff', // LDN: 0/1802
    'HII region': '#00ff00', // M&C: 1/89, NGC: 5/566
    'interstellar matter': '#00ff00', // M&C: 0/89, NGC: 1/566
    'supernova remnant': '#00ff00', // M&C: 1/89, NGC: 1/566
    'unknown': '#ffffff' // M&C: 0/89, NGC: 0/566
};
lat = core.getObserverLocationInfo().latitude;
decLo = lat - 90.0 + altLo;
if (decLo < -90.0)
    decLo = -90.0;
decHi = lat + 90.0 - altLo;
if (decHi > 90.0)
    decHi = 90.0;
atm = StelSkyDrawer.getFlagHasAtmosphere();
aec1 = StelSkyDrawer.getExtinctionCoefficient();
obsKey = 'lat°:' + lat.toFixed(4) + '±' + altLo.toFixed(4) + ',dim°:' + sizeLo.toFixed(6) + '-' + sizeHi.toFixed(6) + ',mag:' + magLo.toFixed(2) + '-' + magHi.toFixed(2) + ',cat:';
cHi = cats.length;
for (c = 0; c < cHi; c++) {
    if (cats[c].use)
        obsKey = obsKey + cats[c].short + '+';
}
obsKey = obsKey.substring(0, obsKey.length - 1);
idList = idLists[obsKey];
n = 0;
txts = [];
StelSkyDrawer.setExtinctionCoefficient(aec);
MarkerMgr.deleteAllMarkers();
core.debug('// ** Calculated Parameters **');
core.debug('date = ' + core.getDate('local').replace('T', ' ') + '; // location datetime for simulation');
core.debug('decLo = ' + decLo + '; // object min declination (°)');
core.debug('decHi = ' + decHi + '; // object max declination (°)');
core.debug('sizeLo = ' + sizeLo + '; // object min diameter (°)');
core.debug('sizeHi = ' + sizeHi + '; // object max diameter (°)');
core.debug('//');
core.debug('// ** Objects (JSON) **');
core.debug('// i: index');
core.debug('// id: catalog designation');
core.debug('// r_a: right ascension (°)');
core.debug('// dec: declination (°)');
core.debug('// azi: azimuth (°) from north towards east');
core.debug('// alt: altitude (°)');
core.debug('// size: diameter (°)');
core.debug('// mag: apparent magnitude');
core.debug('// band: magnitude passband (B, V)');
core.debug('// type: object type');
core.debug('//');
core.debug('// * Ordered by catalog and number *');
core.debug('[');
ids = [];
if (idList)
    cHi = 1;
for (c = 0; c < cHi; c++) {
    if (idList) {
        cat = '';
        iHi = idList.idHi;
    }
    else {
        if (!cats[c].use)
            continue;
        cat = cats[c].short + ' ';
        iHi = cats[c].count;
    }
    for (i = 1; i <= iHi; i++) {
        if (idList)
            id = idList.ids[i];
        else
            id = cat + i;
        info = core.getObjectInfo(id);
        if (!info.found)
            continue;
        if (!idList) {
            des = ' - ' + info.designations + ' - ';
            rx = new RegExp(' - ' + cat + '[0-9]+ - ' , 'g');
            matches = des.match(rx) || [];
            if (matches.length > 1 && ' - ' + $id + ' - ' !== matches[0])
                continue;
            if (cat === 'IC ' && des.indexOf(' - NGC ') >= 0)
                continue;
        }
        size = info['size-dd'];
        if (!size || size < sizeLo || size > sizeHi)
            continue;
        vmag = info.vmag;
        if (!vmag)
            continue;
        else if (vmag !== 99) {
            mag = vmag;
            band = 'V';
        }
        else {
            mag = info.bmag;
            band = 'B';
        }
        if (!mag || mag < magLo || mag > magHi)
            continue;
        vmage = info.vmage;
        if (!vmage)
            mage = mag;
        else
            mage = mag + vmage - vmag;
        type = info.type;
        if (type == 'dark nebula')
            mage = magHi + 0.5 - mag + mage - mag;
        if (mage > magHi)
            continue;
        dec = info.dec;
        if (dec < decLo || dec > decHi)
            continue;
        alt = info.altitude;
        if (atm && alt < altLo)
            continue;
        r_a = info.raJ2000;
        dec = info.decJ2000;
        azi = info.azimuth;
        k = Math.ceil(magHi + 1.0 - mage);
        if (k < 1)
            k = 1;
        if (k > 5)
            k = 5;
        rgbHex = rgbs[type] || rgbs['unknown'];
        rgb = hex2rgb(rgbHex);
        rgbHex = rgb2hex(rgb[0] * k / 5, rgb[1] * k / 5, rgb[2] * k / 5);
        n++;
        txt = '    {"i": ' + n + ', "id": "' + id + '", "r_a": ' + r_a.toFixed(5) + ', "dec": ' + dec.toFixed(5) + ', "azi": ' + azi.toFixed(2) + ', "alt": ' + alt.toFixed(2) + ', "size": ' + size.toFixed(3) + ', "mag": ' + mage.toFixed(2) + ', "band": "' + band + '", "type": "' + type + '"},';
        if (atm)
            txts.push({key: azi, txt: txt});
        else if (!idList)
            ids.push(id);
        core.debug(txt);
        MarkerMgr.markerEquatorial(r_a, dec, true, true, 'circle', rgbHex, diam, false, 0, true);
    }
}
core.debug('    {"i": 0, "id": "NGC 0", "r_a": 0.00000, "dec": 0.00000, "azi": 0.00, "alt": 0.00, "size": 0.000, "mag": 0.00, "band": "V", "type": "dummy"}');
core.debug(']');
if (atm) {
    txts = txts.sort(function(a, b) { return a.key - b.key; });
    core.debug('//');
    core.debug('// * Ordered by azimuth *');
    core.debug('[');
    iHi = txts.length;
    for (i = 0; i < iHi; i++) {
        core.debug(txts[i].txt);
    }
    core.debug('    {"i": 0, "id": "NGC 0", "r_a": 0.00000, "dec": 0.00000, "azi": 0.00, "alt": 0.00, "size": 0.000, "mag": 0.00, "band": "V", "type": "dummy"}');
    core.debug(']');
}
else if (ids.length > 0) {
    core.debug('//');
    core.debug('// * Ids *');
    core.debug('// Speed of rendering can be improved 10X by using a list of possible object ids in file "scripts/mz_vis_obj_ids.inc".');
    core.debug('// Generate list by running script without atmosphere and copy-paste the list from this log to file.');
    core.debug('// Several lists can be kept by copy-paste of the contents of "var idlists = {"..."};" here, that is "lat°:"..."]}",');
    core.debug('// to inside "var idlists = {"..."};" in file, comma separated from any existing list(s) there.');
    core.debug('// contents of file "scripts/mz_vis_obj_ids.inc" begins after this line');
    core.debug('var idLists = {');
    core.debug('    "' + obsKey + '": {idHi: ' + ids.length + ', ids: ["' + ids.join('", "') + '"]}');
    core.debug('};');
    core.debug('// contents of file "scripts/mz_vis_obj_ids.inc" ends before this line');
}
StelSkyDrawer.setExtinctionCoefficient(aec1);
// end