Repository: gammasim/simtools (language: python)

Issue:
Integrate old 'sites' branch to master
As part of #360, we need to develop the array layout (plotting), which was already started in the 'sites' branch (https://github.com/gammasim/gammasim-tools/tree/sites). In order not to duplicate functionalities I am adapting the old code to the new state of the master branch, implementing the documentation and the respective tests. This is part of #360


Discussion / hints:

Fix patch:
diff --git a/applications/make_regular_arrays.py b/applications/make_regular_arrays.py
index c44083aa1..5b1fe8473 100644
--- a/applications/make_regular_arrays.py
+++ b/applications/make_regular_arrays.py
@@ -88,8 +88,7 @@ def main():
         layout_center_data[site]["center_alt"] = (
             float(site_pars_db[site]["altitude"]["Value"]) * u.m
         )
-        # TEMPORARY TODO should go into DB
-        layout_center_data[site]["EPSG"] = corsika_pars["SITE_PARAMETERS"][site]["EPSG"][0]
+        layout_center_data[site]["EPSG"] = site_pars_db[site]["EPSG"]["Value"]
         corsika_telescope_data[site] = dict()
         corsika_telescope_data[site]["corsika_obs_level"] = layout_center_data[site]["center_alt"]
         corsika_telescope_data[site]["corsika_sphere_center"] = corsika_pars[
@@ -107,8 +106,10 @@ def main():
         for array_name in ["1SST", "4SST", "1MST", "4MST", "1LST", "4LST"]:
             logger.info(f"Processing array {array_name}")
             layout = LayoutArray(
+                site=site,
+                mongo_db_config=db_config,
                 label=label,
-                name=site + "-" + array_name,
+                name=f"{site}-{array_name}",
                 layout_center_data=layout_center_data[site],
                 corsika_telescope_data=corsika_telescope_data[site],
             )
diff --git a/applications/print_array_elements.py b/applications/print_array_elements.py
index a3451be46..27d80133e 100644
--- a/applications/print_array_elements.py
+++ b/applications/print_array_elements.py
@@ -131,8 +131,7 @@ def main():
     _logger = logging.getLogger()
     _logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
 
-    layout = layout_array.LayoutArray()
-    layout.read_telescope_list_file(telescope_list_file=args_dict["array_element_list"])
+    layout = layout_array.LayoutArray(telescope_list_file=args_dict["array_element_list"])
     layout.convert_coordinates()
     if args_dict["export"] is not None:
         layout.export_telescope_list(
diff --git a/applications/production.py b/applications/production.py
index 7f651c6e6..4b853dc17 100644
--- a/applications/production.py
+++ b/applications/production.py
@@ -235,6 +235,7 @@ def main():
             config_data=config_data,
             submit_command=args_dict["submit_command"],
             test=args_dict["test"],
+            mongo_db_config=db_config,
         )
 
     if args_dict["showers_only"]:
diff --git a/applications/sim_showers_for_trigger_rates.py b/applications/sim_showers_for_trigger_rates.py
index 942c99c0f..9db1ecfd1 100644
--- a/applications/sim_showers_for_trigger_rates.py
+++ b/applications/sim_showers_for_trigger_rates.py
@@ -124,13 +124,13 @@ def _parse(label=None, description=None):
         type=str,
         default=None,
     )
-    return config.initialize(telescope_model=True, job_submission=True)
+    return config.initialize(telescope_model=True, job_submission=True, db_config=True)
 
 
 def main():
 
     label = Path(__file__).stem
-    args_dict, _ = _parse(
+    args_dict, db_config = _parse(
         label=label, description="Simulate showers to be used for trigger rate calculations"
     )
 
@@ -163,6 +163,7 @@ def main():
         config_data=shower_config_data,
         submit_command=args_dict.get("submit_command", ""),
         test=args_dict["test"],
+        mongo_db_config=db_config,
     )
 
     if not args_dict["test"]:
diff --git a/simtools/corsika/corsika_config.py b/simtools/corsika/corsika_config.py
index 51769b51e..334484db7 100644
--- a/simtools/corsika/corsika_config.py
+++ b/simtools/corsika/corsika_config.py
@@ -49,6 +49,8 @@ class CorsikaConfig:
 
     Parameters
     ----------
+    mongo_db_config: dict
+        MongoDB configuration.
     site: str
         North or South.
     layout_name: str
@@ -58,13 +60,15 @@ class CorsikaConfig:
     corsika_config_data: dict
         CORSIKA user parameters.
     corsika_config_file: str
-        Name of the yaml configuration file.
+        Name of the yaml configuration file. If not provided, \
+        data/parameters/corsika_parameters.yml will be used.
     corsika_parameters_file: str
         Name of the yaml file to set remaining CORSIKA parameters.
     """
 
     def __init__(
         self,
+        mongo_db_config,
         site,
         layout_name,
         label=None,
@@ -90,11 +94,18 @@ def __init__(
         # Grabbing layout name and building LayoutArray
         self.layout_name = names.validate_layout_array_name(layout_name)
         self.layout = LayoutArray.from_layout_array_name(
-            self.site + "-" + self.layout_name, label=self.label
+            mongo_db_config=mongo_db_config,
+            layout_array_name=f"{self.site}-{self.layout_name}",
+            label=self.label,
         )
 
         # Load parameters
-        self._load_corsika_parameters_file(corsika_parameters_file)
+        if corsika_parameters_file is None:
+            corsika_parameters_file = self.io_handler.get_input_data_file(
+                "parameters", "corsika_parameters.yml"
+            )
+
+        self._corsika_parameters = self.load_corsika_parameters_file(corsika_parameters_file)
 
         corsika_config_data = collect_data_from_yaml_or_dict(
             corsika_config_file, corsika_config_data
@@ -109,23 +120,27 @@ def __repr__(self):
         )
         return text
 
-    def _load_corsika_parameters_file(self, filename):
+    @staticmethod
+    def load_corsika_parameters_file(corsika_parameters_file):
         """
-        Load CORSIKA parameters from a given file (filename not None),
-        or from the default parameter file provided in the
-        data directory (filename is None).
+        Load CORSIKA parameters from the provided corsika_parameters_file.
+
+        Parameters
+        ----------
+        corsika_parameters_file: str or Path
+            File with CORSIKA parameters.
+
+        Returns
+        -------
+        corsika_parameters: dict
+            Dictionary with CORSIKA parameters.
         """
-        if filename is not None:
-            # User provided file.
-            self._corsika_parameters_file = filename
-        else:
-            # Default file from data directory.
-            self._corsika_parameters_file = self.io_handler.get_input_data_file(
-                "parameters", "corsika_parameters.yml"
-            )
-        self._logger.debug(f"Loading CORSIKA parameters from file {self._corsika_parameters_file}")
-        with open(self._corsika_parameters_file, "r") as f:
-            self._corsika_parameters = yaml.load(f)
+
+        logger = logging.getLogger(__name__)
+        logger.debug(f"Loading CORSIKA parameters from file {corsika_parameters_file}")
+        with open(corsika_parameters_file, "r") as f:
+            corsika_parameters = yaml.load(f)
+        return corsika_parameters
 
     def set_user_parameters(self, corsika_config_data):
         """
diff --git a/simtools/corsika/corsika_runner.py b/simtools/corsika/corsika_runner.py
index 3e3cd6a7a..0f2203745 100644
--- a/simtools/corsika/corsika_runner.py
+++ b/simtools/corsika/corsika_runner.py
@@ -54,6 +54,8 @@ class CorsikaRunner:
 
     Parameters
     ----------
+    mongo_db_config: dict
+        MongoDB configuration.
     site: str
         South or North.
     layout_name: str
@@ -74,6 +76,7 @@ class CorsikaRunner:
 
     def __init__(
         self,
+        mongo_db_config,
         site,
         layout_name,
         simtel_source_path,
@@ -104,11 +107,11 @@ def __init__(
         corsika_config_data = collect_data_from_yaml_or_dict(
             corsika_config_file, corsika_config_data
         )
-        self._load_corsika_config_data(corsika_config_data)
+        self._load_corsika_config_data(mongo_db_config, corsika_config_data)
 
         self._load_corsika_data_directories()
 
-    def _load_corsika_config_data(self, corsika_config_data):
+    def _load_corsika_config_data(self, mongo_db_config, corsika_config_data):
         """Reads corsika_config_data, creates corsika_config and corsika_input_file."""
 
         corsika_data_directory_from_config = corsika_config_data.get("data_directory", None)
@@ -135,6 +138,7 @@ def _load_corsika_config_data(self, corsika_config_data):
         # in corsika_config_data
         try:
             self.corsika_config = CorsikaConfig(
+                mongo_db_config=mongo_db_config,
                 site=self.site,
                 label=self.label,
                 layout_name=self.layout_name,
diff --git a/simtools/layout/layout_array.py b/simtools/layout/layout_array.py
index 00458bb51..501286452 100644
--- a/simtools/layout/layout_array.py
+++ b/simtools/layout/layout_array.py
@@ -1,14 +1,16 @@
 import logging
+from pathlib import Path
 
 import astropy.units as u
 import numpy as np
 import pyproj
-from astropy.table import Table
+from astropy.table import QTable
 
-from simtools import io_handler
+from simtools import db_handler, io_handler
 from simtools.layout.telescope_position import TelescopePosition
 from simtools.util import names
 from simtools.util.general import collect_data_from_yaml_or_dict
+from simtools.util.names import all_telescope_class_names
 
 __all__ = ["InvalidTelescopeListFile", "LayoutArray"]
 
@@ -23,6 +25,10 @@ class LayoutArray:
 
     Parameters
     ----------
+    mongo_db_config: dict
+        MongoDB configuration.
+    site: str
+        Site name or location (e.g., North/South or LaPalma/Paranal)
     label: str
         Instance label.
     name: str
@@ -37,6 +43,8 @@ class LayoutArray:
 
     def __init__(
         self,
+        mongo_db_config=None,
+        site=None,
         label=None,
         name=None,
         layout_center_data=None,
@@ -50,27 +58,30 @@ def __init__(
         self._logger = logging.getLogger(__name__)
         self._logger.debug("Init LayoutArray")
 
+        self.mongo_db_config = mongo_db_config
         self.label = label
         self.name = name
+        self.site = None if site is None else names.validate_site_name(site)
         self.io_handler = io_handler.IOHandler()
 
         self._telescope_list = []
         self._epsg = None
-        self.telescope_list_file = None
         if telescope_list_file is None:
             self._initialize_coordinate_systems(layout_center_data)
             self._initialize_corsika_telescope(corsika_telescope_data)
         else:
-            self.read_telescope_list_file(telescope_list_file)
+            self.initialize_layout_array_from_telescope_file(telescope_list_file)
 
     @classmethod
-    def from_layout_array_name(cls, layout_array_name, label=None):
+    def from_layout_array_name(cls, mongo_db_config, layout_array_name, label=None):
         """
         Read telescope list from file for given layout name (e.g. South-4LST, North-Prod5, ...).
         Layout definitions are given in the `data/layout` path.
 
         Parameters
         ----------
+        mongo_db_config: dict
+            MongoDB configuration.
         layout_array_name: str
             e.g. South-4LST, North-Prod5 ...
         label: str
@@ -82,17 +93,22 @@ def from_layout_array_name(cls, layout_array_name, label=None):
             Instance of the LayoutArray.
         """
 
-        spl = layout_array_name.split("-")
-        site_name = names.validate_site_name(spl[0])
-        array_name = names.validate_layout_array_name(spl[1])
+        split_name = layout_array_name.split("-")
+        site_name = names.validate_site_name(split_name[0])
+        array_name = names.validate_layout_array_name(split_name[1])
         valid_layout_array_name = site_name + "-" + array_name
 
-        layout = cls(name=valid_layout_array_name, label=label)
+        layout = cls(
+            site=site_name,
+            mongo_db_config=mongo_db_config,
+            name=valid_layout_array_name,
+            label=label,
+        )
 
         telescope_list_file = layout.io_handler.get_input_data_file(
             "layout", f"telescope_positions-{valid_layout_array_name}.ecsv"
         )
-        layout.read_telescope_list_file(telescope_list_file)
+        layout.initialize_layout_array_from_telescope_file(telescope_list_file)
 
         return layout
 
@@ -104,7 +120,7 @@ def __getitem__(self, i):
 
     def _initialize_corsika_telescope(self, corsika_dict=None):
         """
-        Initialize Dictionary for CORSIKA telescope parameters. Allow input from different sources\
+        Initialize Dictionary for CORSIKA telescope parameters. Allow input from different sources
         (dictionary, yaml, ecsv header), which require checks to handle units correctly.
 
         Parameters
@@ -115,17 +131,85 @@ def _initialize_corsika_telescope(self, corsika_dict=None):
         """
         self._corsika_telescope = {}
 
-        if corsika_dict is not None:
-            self._logger.debug(f"Initialize CORSIKA telescope parameters from dict: {corsika_dict}")
-            self._initialize_corsika_telescope_from_dict(corsika_dict)
-        else:
+        if corsika_dict is None:
             self._logger.debug("Initialize CORSIKA telescope parameters from file")
-            self._initialize_corsika_telescope_from_dict(
-                collect_data_from_yaml_or_dict(
-                    self.io_handler.get_input_data_file("parameters", "corsika_parameters.yml"),
-                    None,
-                )
+            corsika_dict = self._from_corsika_file_to_dict()
+        else:
+            self._logger.debug(f"Initialize CORSIKA telescope parameters from dict: {corsika_dict}")
+
+        self._initialize_corsika_telescope_from_dict(corsika_dict)
+
+    def _from_corsika_file_to_dict(self, file_name=None):
+        """
+        Get the corsika parameter file and return a dictionary with the keys necessary to
+        initialize this class.
+
+        Parameters
+        ----------
+        file_name: str or Path
+            File from which to extract the corsika parameters. Default is
+            data/parameters/corsika_parameters.yml
+
+        Returns
+        ------
+        corsika_dict:
+            Dictionary with corsika telescopes information.
+
+        Raises
+        ------
+        FileNotFoundError:
+            If file_name does not exist.
+        """
+        logger = logging.getLogger(__name__)
+        if file_name is None:
+            corsika_parameters_dict = collect_data_from_yaml_or_dict(
+                self.io_handler.get_input_data_file("parameters", "corsika_parameters.yml"), None
             )
+        else:
+            if not isinstance(file_name, Path):
+                file_name = Path(file_name)
+            if file_name.exists():
+                corsika_parameters_dict = collect_data_from_yaml_or_dict(file_name, None)
+            else:
+                raise FileNotFoundError
+
+        corsika_dict = {}
+        corsika_pars = ["corsika_sphere_radius", "corsika_sphere_center"]
+        for simtools_par in corsika_pars:
+            corsika_par = names.translate_simtools_to_corsika(simtools_par)
+            corsika_dict[simtools_par] = {}
+            for tel_type in all_telescope_class_names:
+                corsika_dict[simtools_par][tel_type] = corsika_parameters_dict[corsika_par][
+                    tel_type
+                ]["value"]
+
+                try:
+                    unit = corsika_parameters_dict[corsika_par][tel_type]["unit"]
+                    corsika_dict[simtools_par][tel_type] = corsika_dict[simtools_par][
+                        tel_type
+                    ] * u.Unit(unit)
+                except KeyError:
+                    logger.warning(
+                        "Key not valid. Dictionary does not have a key 'unit'. Continuing without "
+                        "the unit."
+                    )
+                    corsika_dict[simtools_par][tel_type] = corsika_dict[simtools_par][tel_type]
+
+        if self.mongo_db_config is None:
+            self._logger.error("DB connection info was not provided, cannot set site altitude")
+            raise ValueError
+        if self.site is None:
+            self._logger.error("Site was not provided, cannot set site altitude")
+            raise ValueError
+
+        db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
+        self._logger.debug("Reading site parameters from DB")
+        _site_pars = db.get_site_parameters(self.site, "Current", only_applicable=True)
+        corsika_dict["corsika_obs_level"] = _site_pars["altitude"]["Value"] * u.Unit(
+            _site_pars["altitude"]["units"]
+        )
+
+        return corsika_dict
 
     @staticmethod
     def _initialize_sphere_parameters(sphere_dict):
@@ -167,7 +251,6 @@ def _initialize_corsika_telescope_from_dict(self, corsika_dict):
             dictionary with CORSIKA telescope parameters
 
         """
-
         try:
             self._corsika_telescope["corsika_obs_level"] = u.Quantity(
                 corsika_dict["corsika_obs_level"]
@@ -262,20 +345,19 @@ def _altitude_from_corsika_z(self, pos_z=None, altitude=None, tel_name=None):
             Altitude or CORSIKA z-coordinate (np.nan in case of ill-defined value).
 
         """
-
         if pos_z is not None and altitude is None:
             return TelescopePosition.convert_telescope_altitude_from_corsika_system(
                 pos_z,
                 self._corsika_telescope["corsika_obs_level"],
                 self._get_corsika_sphere_center(tel_name),
             )
+
         if altitude is not None and pos_z is None:
             return TelescopePosition.convert_telescope_altitude_to_corsika_system(
                 altitude,
                 self._corsika_telescope["corsika_obs_level"],
                 self._get_corsika_sphere_center(tel_name),
             )
-
         return np.nan
 
     def _get_corsika_sphere_center(self, tel_name):
@@ -294,16 +376,16 @@ def _get_corsika_sphere_center(self, tel_name):
 
         """
 
-        if self.get_telescope_type(tel_name) is not None:
+        if names.get_telescope_type(tel_name) is not None:
             return self._corsika_telescope["corsika_sphere_center"][
-                self.get_telescope_type(tel_name)
+                names.get_telescope_type(tel_name)
             ]
 
         return 0.0 * u.m
 
     def _load_telescope_names(self, row):
         """
-        Read and set telescope names
+        Read and set telescope names.
 
         Parameters
         ----------
@@ -326,7 +408,7 @@ def _load_telescope_names(self, row):
         try:
             tel.name = row["telescope_name"]
             if "asset_code" not in row:
-                tel.asset_code = self.get_telescope_type(tel.name)
+                tel.asset_code = names.get_telescope_type(tel.name)
         except KeyError:
             pass
         try:
@@ -347,58 +429,118 @@ def _load_telescope_names(self, row):
 
         return tel
 
+    def _assign_unit_to_quantity(self, value, unit):
+        """
+        Assign unit to quantity.
+
+        Parameters
+        ----------
+        value:
+            value to get a unit. It can be a float, int, or a Quantity (convertible to 'unit').
+        unit: astropy.units.Unit
+            Unit to apply to 'quantity'.
+
+        Returns
+        -------
+        astropy.units.Quantity
+            Quantity of value 'quantity' and unit 'unit'.
+        """
+        if isinstance(value, u.Quantity):
+            self._logger.debug(f"Value {value} is already of type astropy.units.Quantity.")
+            if isinstance(value.unit, type(unit)):
+                self._logger.debug(f"Quantity {value} has already unit {unit}. Returning {value}")
+                return value
+            else:
+                try:
+                    value = value.to(unit)
+                    return value
+                except u.UnitConversionError:
+                    self._logger.error(f"Cannot convert {value.unit} to {unit}.")
+                    raise
+        return value * unit
+
+    def _try_set_coordinate(self, row, tel, table, crs_name, key1, key2):
+        """Function auxiliary to self._load_telescope_list. It sets the coordinates.
+
+        Parameters
+        ----------
+        row: dict
+            A row of the astropy.table.Table with array element coordinates.
+        tel: TelescopePosition
+            Instance of TelescopePosition.
+        table: astropy.table.Table or astropy.table.QTable
+            data table with array element coordinates.
+        crs_name: str
+            Name of coordinate system.
+        key1: str
+            Name of x-coordinate.
+        key2: str
+            Name of y-coordinate.
+        """
+        try:
+            tel.set_coordinates(
+                crs_name,
+                self._assign_unit_to_quantity(row[key1], table[key1].unit),
+                self._assign_unit_to_quantity(row[key2], table[key2].unit),
+            )
+            self._logger.debug(f"{key1} and {key2} are given. Setting coordinates.")
+
+        except KeyError:
+            self._logger.debug(f"{key1} and {key2} are not given. Coordinates not set.")
+
+    def _try_set_altitude(self, row, tel, table):
+        """
+        Function auxiliary to self._load_telescope_list. It sets the altitude of the
+        TelescopePosition instance.
+
+        Parameters
+        ----------
+        row: dict
+            A row of the astropy.table.Table with array element coordinates.
+        tel: TelescopePosition
+            Instance of TelescopePosition.
+        table: astropy.table.Table or astropy.table.QTable
+            data table with array element coordinates.
+        """
+        try:
+            tel.set_altitude(
+                self._altitude_from_corsika_z(
+                    pos_z=self._assign_unit_to_quantity(row["pos_z"], table["pos_z"].unit),
+                    tel_name=tel.name,
+                )
+            )
+            self._logger.debug(
+                "CORSIKA z-coordinate of telescope is given. Setting altitude from it."
+            )
+        except KeyError:
+            pass
+        try:
+            tel.set_altitude(self._assign_unit_to_quantity(row["alt"], table["alt"].unit))
+            self._logger.debug("Telescope altitude is given. Setting altitude from it.")
+        except KeyError:
+            pass
+
     def _load_telescope_list(self, table):
         """
-        Load list of telescope from an astropy table
+        Load list of telescope from an astropy table (support both QTable and Table)
 
         Parameters
         ----------
-        table: astropy.table
+        table: astropy.table.Table or astropy.table.QTable
             data table with array element coordinates
 
         """
-
         for row in table:
             tel = self._load_telescope_names(row)
-
-            try:
-                tel.set_coordinates(
-                    "corsika",
-                    row["pos_x"] * table["pos_x"].unit,
-                    row["pos_y"] * table["pos_y"].unit,
-                )
-            except KeyError:
-                pass
-            try:
-                tel.set_coordinates(
-                    "utm",
-                    row["utm_east"] * table["utm_east"].unit,
-                    row["utm_north"] * table["utm_north"].unit,
-                )
-            except KeyError:
-                pass
-            try:
-                tel.set_coordinates(
-                    "mercator", row["lat"] * table["lat"].unit, row["lon"] * table["lon"].unit
-                )
-            except KeyError:
-                pass
-            try:
-                tel.set_altitude(
-                    self._altitude_from_corsika_z(
-                        pos_z=row["pos_z"] * table["pos_z"].unit, tel_name=tel.name
-                    )
-                )
-            except KeyError:
-                pass
-            try:
-                tel.set_altitude(row["alt"] * table["alt"].unit)
-            except KeyError:
-                pass
+            self._try_set_coordinate(row, tel, table, "corsika", "pos_x", "pos_y")
+            self._try_set_coordinate(row, tel, table, "utm", "utm_east", "utm_north")
+            self._try_set_coordinate(row, tel, table, "mercator", "mercator", "lon")
+            self._try_set_altitude(row, tel, table)
 
             self._telescope_list.append(tel)
 
-    def read_telescope_list_file(self, telescope_list_file):
+    @staticmethod
+    def read_telescope_list_file(telescope_list_file):
         """
         Read list of telescopes from a ecsv file.
 
@@ -407,20 +549,37 @@ def read_telescope_list_file(self, telescope_list_file):
         telescope_list_file: str or Path
             Path to the telescope list file.
 
+        Returns
+        -------
+        astropy.QTable
+            Astropy table with the telescope layout information.
+
         Raises
         ------
         FileNotFoundError
             If file cannot be opened.
 
         """
+        _logger = logging.getLogger(__name__)
         try:
-            table = Table.read(telescope_list_file, format="ascii.ecsv")
+            table = QTable.read(telescope_list_file, format="ascii.ecsv")
         except FileNotFoundError:
-            self._logger.error(f"Error reading list of array elements from {telescope_list_file}")
+            _logger.error(f"Error reading list of array elements from {telescope_list_file}")
             raise
+        _logger.info(f"Reading array elements from {telescope_list_file}")
+
+        return table
 
-        self._logger.info(f"Reading array elements from {telescope_list_file}")
+    def initialize_layout_array_from_telescope_file(self, telescope_list_file):
+        """
+        Initialize the Layout array from a telescope list file.
 
+        Parameters
+        ----------
+        telescope_list_file: str or Path
+            Path to the telescope list file.
+        """
+        table = self.read_telescope_list_file(telescope_list_file)
         self._initialize_corsika_telescope(table.meta)
         self._initialize_coordinate_systems(table.meta)
         self._load_telescope_list(table)
@@ -529,10 +688,10 @@ def export_telescope_list(self, crs_name, corsika_z=False):
             Write telescope height in CORSIKA coordinates (for CORSIKA system).
         """
 
-        table = Table(meta=self._get_export_metadata(crs_name == "corsika"))
+        table = QTable(meta=self._get_export_metadata(crs_name == "corsika"))
 
-        tel_names, asset_code, sequence_number, geo_code = list(), list(), list(), list()
-        pos_x, pos_y, pos_z = list(), list(), list()
+        tel_names, asset_code, sequence_number, geo_code = [], [], [], []
+        pos_x, pos_y, pos_z = [], [], []
         for tel in self._telescope_list:
             tel_names.append(tel.name)
             asset_code.append(tel.asset_code)
@@ -602,7 +761,7 @@ def get_corsika_input_list(self):
             pos_x, pos_y, pos_z = tel.get_coordinates("corsika")
             try:
                 sphere_radius = self._corsika_telescope["corsika_sphere_radius"][
-                    self.get_telescope_type(tel.name)
+                    names.get_telescope_type(tel.name)
                 ]
             except KeyError:
                 self._logger.error("Missing definition of CORSIKA sphere radius")
@@ -761,27 +920,23 @@ def _get_crs_wgs84():
         return pyproj.CRS("EPSG:4326")
 
     @staticmethod
-    def get_telescope_type(telescope_name):
+    def include_radius_into_telescope_table(telescope_table):
         """
-        Guess telescope type from name. Types are "LST", "MST", "SST", "SCT".
+        Include the radius of the telescopes types into the astropy.table.QTable telescopes_table
 
         Parameters
         ----------
-        telescope_name: str
-            Telescope name
+        telescope_table: astropy.QTable
+            Astropy QTable with telescope information.
 
         Returns
         -------
-        str
-            Telescope type.
+        astropy.QTable
+            Astropy QTable with telescope information updated with the radius.
         """
 
-        _class, _ = names.split_telescope_model_name(telescope_name)
-        try:
-            if _class[0:3] in ("LST", "MST", "SST", "SCT"):
-                return _class[0:3]
-
-        except IndexError:
-            pass
-
-        return ""
+        telescope_table["radius"] = [
+            telescope_table.meta["corsika_sphere_radius"][names.get_telescope_type(tel_name_now)]
+            for tel_name_now in telescope_table["telescope_name"]
+        ]
+        return telescope_table
diff --git a/simtools/model/array_model.py b/simtools/model/array_model.py
index b70be1ad6..af74ce4c7 100644
--- a/simtools/model/array_model.py
+++ b/simtools/model/array_model.py
@@ -38,6 +38,7 @@ def __init__(self, mongo_db_config, label=None, array_config_file=None, array_co
         """
         self._logger = logging.getLogger(__name__)
         self._logger.debug("Init ArrayModel")
+        self.mongo_db_config = mongo_db_config
         self.label = label
         self.site = None
         self.layout = None
@@ -48,7 +49,7 @@ def __init__(self, mongo_db_config, label=None, array_config_file=None, array_co
         array_config_data = collect_data_from_yaml_or_dict(array_config_file, array_config_data)
         self._load_array_data(array_config_data)
         self._set_config_file_directory()
-        self._build_array_model(mongo_db_config)
+        self._build_array_model()
         self._telescope_model_files_exported = False
         self._array_model_file_exported = False
 
@@ -83,7 +84,8 @@ def _load_array_data(self, array_config_data):
         # Grabbing layout name and building LayoutArray
         self.layout_name = names.validate_layout_array_name(array_config_data["layout_name"])
         self.layout = LayoutArray.from_layout_array_name(
-            self.site + "-" + self.layout_name,
+            mongo_db_config=self.mongo_db_config,
+            layout_array_name=self.site + "-" + self.layout_name,
             label=self.label,
         )
 
@@ -134,20 +136,15 @@ def _set_config_file_directory(self):
             self._config_file_directory.mkdir(parents=True, exist_ok=True)
             self._logger.info(f"Creating directory {self._config_file_directory}")
 
-    def _build_array_model(self, mongo_db_config):
+    def _build_array_model(self):
         """
         Build the site parameters and the list of telescope models,
         including reading the parameters from the DB.
 
-        Parameters
-        ----------
-        mongo_db_config: str
-            MongoDB configuration.
-
         """
 
         # Getting site parameters from DB
-        db = db_handler.DatabaseHandler(mongo_db_config=mongo_db_config)
+        db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
         self._site_parameters = db.get_site_parameters(
             self.site, self.model_version, only_applicable=True
         )
@@ -157,7 +154,7 @@ def _build_array_model(self, mongo_db_config):
         _all_telescope_model_names = list()  # List of telescope names without repetition
         _all_pars_to_change = dict()
         for tel in self.layout:
-            tel_size = self.layout.get_telescope_type(tel.name)
+            tel_size = names.get_telescope_type(tel.name)
 
             # Collecting telescope name and pars to change from array_config_data
             tel_model_name, pars_to_change = self._get_single_telescope_info_from_array_config(
@@ -177,7 +174,7 @@ def _build_array_model(self, mongo_db_config):
                     telescope_model_name=tel_model_name,
                     model_version=self.model_version,
                     label=self.label,
-                    mongo_db_config=mongo_db_config,
+                    mongo_db_config=self.mongo_db_config,
                 )
             else:
                 # Telescope name already exists.
diff --git a/simtools/simulator.py b/simtools/simulator.py
index 5f7cac5f1..5a364518a 100644
--- a/simtools/simulator.py
+++ b/simtools/simulator.py
@@ -324,6 +324,7 @@ def _set_corsika_runner(self):
 
         """
         self._simulation_runner = CorsikaRunner(
+            mongo_db_config=self._mongo_db_config,
             label=self.label,
             site=self.site,
             layout_name=self.layout_name,
diff --git a/simtools/util/general.py b/simtools/util/general.py
index c82169d1e..c9a77d2b5 100644
--- a/simtools/util/general.py
+++ b/simtools/util/general.py
@@ -7,6 +7,8 @@
 from pathlib import Path
 
 import astropy.units as u
+import numpy as np
+from astropy.coordinates.errors import UnitsError
 from astropy.io.misc import yaml
 
 __all__ = [
@@ -18,6 +20,7 @@
     "MissingRequiredConfigEntry",
     "UnableToIdentifyConfigEntry",
     "get_log_level_from_user",
+    "rotate",
     "separate_args_and_config_data",
     "set_default_kwargs",
     "sort_arrays",
@@ -647,3 +650,80 @@ def change_dict_keys_case(data_dict, lower_case=True):
         raise
 
     return _return_dict
+
+
+def rotate(rotation_angle, x, y):
+    """
+    Rotate x and y by the rotation angle given in rotation_angle, in degrees.
+    The function returns the rotated x and y values in the same unit given.
+    The direction of rotation is clockwise.
+
+    Parameters
+    ----------
+    rotation_angle: astropy.units.deg or astropy.units.rad
+        Angle to rotate the array in degrees.
+    x: numpy.array or list
+        x positions of the telescopes, usually in meters.
+    y: numpy.array or list
+        y positions of the telescopes, usually in meters.
+
+    Returns
+    -------
+    2-tuple of list
+        x and y positions of the rotated telescopes positions.
+
+    Raises
+    ------
+    TypeError:
+        If type of x and y parameters are not valid.
+    RuntimeError:
+        If the length of x and y are different.
+    UnitsError:
+        If the unit of x and y are different.
+        If unit of rotation_angle is neither u.deg nor u.rad.
+    """
+    allowed_types = (list, np.ndarray, u.Quantity, float, int)
+    if not all(isinstance(variable, allowed_types) for variable in [x, y]):
+        raise TypeError("x and y types are not valid! Cannot perform transformation.")
+
+    if (
+        np.sum(
+            np.array([isinstance(x, type_now) for type_now in allowed_types[:-2]])
+            * np.array([isinstance(y, type_now) for type_now in allowed_types[:-2]])
+        )
+        == 0
+    ):
+        raise TypeError("x and y are not from the same type! Cannot perform transformation.")
+
+    if not isinstance(x, (list, np.ndarray)):
+        x = [x]
+    if not isinstance(y, (list, np.ndarray)):
+        y = [y]
+
+    if len(x) != len(y):
+        raise RuntimeError(
+            "Cannot perform coordinate transformation when x and y have different lengths."
+        )
+    if all(isinstance(variable, (u.Quantity)) for variable in [x, y]):
+        if not isinstance(x[0].unit, type(y[0].unit)):
+            raise UnitsError(
+                "Cannot perform coordinate transformation when x and y have different units."
+            )
+    if isinstance(rotation_angle, u.Quantity):
+
+        if isinstance(rotation_angle.unit, type(u.rad)):
+            rotation_angle = rotation_angle.to(u.deg)
+        elif isinstance(rotation_angle.unit, type(u.deg)):
+            pass
+        else:
+            raise u.UnitConversionError(
+                f"Invalid unit for {rotation_angle}. Valid units are u.deg or u.rad."
+            )
+
+    x_trans, y_trans = [np.zeros_like(x).astype(float) for i in range(2)]
+    rotation_angle = rotation_angle.to(u.rad).value
+
+    for step, _ in enumerate(x):
+        x_trans[step] = x[step] * np.cos(rotation_angle) + y[step] * np.sin(rotation_angle)
+        y_trans[step] = (-1) * x[step] * np.sin(rotation_angle) + y[step] * np.cos(rotation_angle)
+    return x_trans, y_trans
diff --git a/simtools/util/names.py b/simtools/util/names.py
index 0c9158e88..85b346476 100644
--- a/simtools/util/names.py
+++ b/simtools/util/names.py
@@ -6,6 +6,7 @@
     "camera_efficiency_simtel_file_name",
     "convert_telescope_model_name_to_yaml",
     "get_site_from_telescope_name",
+    "get_telescope_type",
     "is_valid_name",
     "layout_telescope_list_file_name",
     "ray_tracing_file_name",
@@ -27,6 +28,81 @@
     "validate_telescope_name_db",
 ]
 
+lst = "LST"
+mst = "MST"
+sct = "SCT"
+sst = "SST"
+
+all_telescope_class_names = {
+    lst: ["lst"],
+    mst: ["mst"],
+    sct: ["sct"],
+    sst: ["sst"],
+}
+
+all_camera_names = {
+    "SST": ["sst"],
+    "ASTRI": ["astri"],
+    "GCT": ["gct", "gct-s"],
+    "1M": ["1m"],
+    "FlashCam": ["flashcam", "flash-cam"],
+    "NectarCam": ["nectarcam", "nectar-cam"],
+    "SCT": ["sct"],
+    "LST": ["lst"],
+}
+
+all_structure_names = {"Structure": ["Structure", "structure"]}
+
+all_site_names = {"South": ["paranal", "south"], "North": ["lapalma", "north"]}
+
+all_model_version_names = {
+    "2015-07-21": [""],
+    "2015-10-20-p1": [""],
+    "prod4-v0.0": [""],
+    "prod4-v0.1": [""],
+    "2018-02-16": [""],
+    "prod3_compatible": ["p3", "prod3", "prod3b"],
+    "prod4": ["p4"],
+    "post_prod3_updates": [""],
+    "2016-12-20": [""],
+    "2018-11-07": [""],
+    "2019-02-22": [""],
+    "2019-05-13": [""],
+    "2019-11-20": [""],
+    "2019-12-30": [""],
+    "2020-02-26": [""],
+    "2020-06-28": ["prod5"],
+    "prod4-prototype": [""],
+    "default": [],
+    "Current": [],
+    "Latest": [],
+}
+
+all_simtel_mode_names = {
+    "RayTracing": ["raytracing", "ray-tracing"],
+    "RayTracingSingleMirror": [
+        "raytracing-singlemirror",
+        "ray-tracing-singlemirror",
+        "ray-tracing-single-mirror",
+    ],
+    "Trigger": ["trigger"],
+}
+
+all_layout_array_names = {
+    "4LST": ["4-lst", "4lst"],
+    "1LST": ["1-lst", "1lst"],
+    "4MST": ["4-mst", "4mst"],
+    "1MST": ["1-mst", "mst"],
+    "4SST": ["4-sst", "4sst"],
+    "1SST": ["1-sst", "sst"],
+    "Prod5": ["prod5", "p5"],
+    "TestLayout": ["test-layout"],
+}
+
+corsika_to_simtools_names = {
+    "OBSLEV": "corsika_obs_level",
+}
+
 
 def validate_sub_system_name(name):
     """
@@ -353,73 +429,6 @@ def convert_telescope_model_name_to_yaml(name):
     return old_names[new_name]
 
 
-all_telescope_class_names = {
-    "SST": ["sst"],
-    "MST": ["mst"],
-    "SCT": ["sct"],
-    "LST": ["lst"],
-}
-
-all_camera_names = {
-    "SST": ["sst"],
-    "ASTRI": ["astri"],
-    "GCT": ["gct", "gct-s"],
-    "1M": ["1m"],
-    "FlashCam": ["flashcam", "flash-cam"],
-    "NectarCam": ["nectarcam", "nectar-cam"],
-    "SCT": ["sct"],
-    "LST": ["lst"],
-}
-
-all_structure_names = {"Structure": ["Structure", "structure"]}
-
-all_site_names = {"South": ["paranal", "south"], "North": ["lapalma", "north"]}
-
-all_model_version_names = {
-    "2015-07-21": [""],
-    "2015-10-20-p1": [""],
-    "prod4-v0.0": [""],
-    "prod4-v0.1": [""],
-    "2018-02-16": [""],
-    "prod3_compatible": ["p3", "prod3", "prod3b"],
-    "prod4": ["p4"],
-    "post_prod3_updates": [""],
-    "2016-12-20": [""],
-    "2018-11-07": [""],
-    "2019-02-22": [""],
-    "2019-05-13": [""],
-    "2019-11-20": [""],
-    "2019-12-30": [""],
-    "2020-02-26": [""],
-    "2020-06-28": ["prod5"],
-    "prod4-prototype": [""],
-    "default": [],
-    "Current": [],
-    "Latest": [],
-}
-
-all_simtel_mode_names = {
-    "RayTracing": ["raytracing", "ray-tracing"],
-    "RayTracingSingleMirror": [
-        "raytracing-singlemirror",
-        "ray-tracing-singlemirror",
-        "ray-tracing-single-mirror",
-    ],
-    "Trigger": ["trigger"],
-}
-
-all_layout_array_names = {
-    "4LST": ["4-lst", "4lst"],
-    "1LST": ["1-lst", "1lst"],
-    "4MST": ["4-mst", "4mst"],
-    "1MST": ["1-mst", "mst"],
-    "4SST": ["4-sst", "4sst"],
-    "1SST": ["1-sst", "sst"],
-    "Prod5": ["prod5", "p5"],
-    "TestLayout": ["test-layout"],
-}
-
-
 def simtools_instrument_name(site, telescope_class_name, sub_system_name, telescope_id_name):
     """
     Instrument name following gammasim-tools naming convention
@@ -748,3 +757,73 @@ def camera_efficiency_log_file_name(site, telescope_model_name, zenith_angle, la
     name += f"_{label}" if label is not None else ""
     name += ".log"
     return name
+
+
+def get_telescope_type(telescope_name):
+    """
+    Guess telescope type from name, e.g. "LST", "MST", ...
+
+    Parameters
+    ----------
+    telescope_name: str
+        Telescope name
+
+    Returns
+    -------
+    str
+        Telescope type.
+    """
+
+    _class, _ = split_telescope_model_name(telescope_name)
+    try:
+        if _class[0:3] in all_telescope_class_names:
+            return _class[0:3]
+
+    except IndexError:
+        pass
+
+    return ""
+
+
+def translate_corsika_to_simtools(corsika_par):
+    """
+    Translate the name of a CORSIKA parameter to the name used in simtools.
+
+    Parameters
+    ----------
+    corsika_par: str
+        Name of the corsika parameter to be translated.
+
+    """
+
+    try:
+        return corsika_to_simtools_names[corsika_par]
+    except KeyError:
+        _logger = logging.getLogger(__name__)
+        msg = f"Translation not found. We will proceed with the original parameter name:\
+            {corsika_par}."
+        _logger.debug(msg)
+        return corsika_par
+
+
+def translate_simtools_to_corsika(simtools_par):
+    """
+    Translate the name of a simtools parameter to the name used in CORSIKA.
+
+    Parameters
+    ----------
+    simtools_par: str
+        Name of the simtools parameter to be translated.
+    """
+
+    simtools_to_corsika_names = {
+        new_key: new_value for new_value, new_key in corsika_to_simtools_names.items()
+    }
+    try:
+        return simtools_to_corsika_names[simtools_par]
+    except KeyError:
+        _logger = logging.getLogger(__name__)
+        msg = f"Translation not found. We will proceed with the original parameter name:\
+            {simtools_par}."
+        _logger.debug(msg)
+        return simtools_par
diff --git a/simtools/visualization/__init__.py b/simtools/visualization/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/simtools/visualization/legend_handlers.py b/simtools/visualization/legend_handlers.py
index b05aef6e1..c55f258da 100644
--- a/simtools/visualization/legend_handlers.py
+++ b/simtools/visualization/legend_handlers.py
@@ -2,6 +2,11 @@
 import matplotlib.patches as mpatches
 import numpy as np
 
+from simtools.corsika.corsika_config import CorsikaConfig
+from simtools.io_handler import IOHandler
+from simtools.util import names
+from simtools.util.names import lst, mst, sct, sst
+
 __all__ = [
     "EdgePixelObject",
     "HexEdgePixelHandler",
@@ -15,55 +20,72 @@
     "MeanRadiusOuterEdgeObject",
     "OffPixelObject",
     "PixelObject",
+    "SCTHandler",
+    "SCTObject",
     "SSTHandler",
     "SSTObject",
     "SquareEdgePixelHandler",
     "SquareOffPixelHandler",
     "SquarePixelHandler",
+    "TelescopeHandler",
 ]
 
 
+class TelescopeHandler(object):
+    """
+    Telescope handler that centralizes the telescope information. Individual telescopes handlers
+    inherit from this class.
+    """
+
+    def __init__(self):
+        io_handler = IOHandler()
+        corsika_parameters_file = io_handler.get_input_data_file(
+            "parameters", "corsika_parameters.yml"
+        )
+        corsika_info = CorsikaConfig.load_corsika_parameters_file(corsika_parameters_file)
+
+        self.radius_dict = {}
+        self.colors_dict = {
+            lst: "darkorange",
+            mst: "dodgerblue",
+            sct: "black",
+            sst: "lightsteelblue",
+        }
+        for tel_type in names.all_telescope_class_names:
+            self.radius_dict[tel_type] = corsika_info["corsika_sphere_radius"][tel_type]["value"]
+
+
 class PixelObject(object):
     """Pixel Object."""
 
-    pass
-
 
 class EdgePixelObject(object):
     """Edge-Pixel Object."""
 
-    pass
-
 
 class OffPixelObject(object):
     """Off-Pixel Object."""
 
-    pass
-
 
 class LSTObject(object):
     """LST Object."""
 
-    pass
-
 
 class MSTObject(object):
     """MST Object."""
 
-    pass
+
+class SCTObject(object):
+    """SCT Object."""
 
 
 class SSTObject(object):
     """SST Object."""
 
-    pass
-
 
 class MeanRadiusOuterEdgeObject(object):
     """Object for Mean radius outer edge."""
 
-    pass
-
 
 class HexPixelHandler(object):
     """
@@ -200,13 +222,12 @@ def legend_artist(legend, orig_handle, fontsize, handlebox):
         return patch
 
 
-class LSTHandler(object):
+class LSTHandler(TelescopeHandler):
     """
     Legend handler class to plot a representation of an LST in an array layout.
     """
 
-    @staticmethod
-    def legend_artist(legend, orig_handle, fontsize, handlebox):
+    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
         center = (
             handlebox.xdescent + 0.3 * handlebox.width,
             handlebox.ydescent + 0.5 * handlebox.height,
@@ -214,43 +235,43 @@ def legend_artist(legend, orig_handle, fontsize, handlebox):
         radius = handlebox.height
         patch = mpatches.Circle(
             xy=center,
-            radius=radius,
+            radius=radius * self.radius_dict[lst] / self.radius_dict[lst],
             facecolor="none",
-            edgecolor="darkorange",
+            edgecolor=self.colors_dict[lst],
             transform=handlebox.get_transform(),
         )
         handlebox.add_artist(patch)
         return patch
 
 
-class MSTHandler(object):
+class MSTHandler(TelescopeHandler):
     """
     Legend handler class to plot a representation of an MST in an array layout.
     """
 
-    @staticmethod
-    def legend_artist(legend, orig_handle, fontsize, handlebox):
-        x0, y0 = handlebox.xdescent + 0.1 * handlebox.width, handlebox.ydescent
-        width = height = handlebox.height
-        patch = mpatches.Rectangle(
-            [x0, y0],
-            width,
-            height,
-            facecolor="dodgerblue",
-            edgecolor="dodgerblue",
+    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
+        center = (
+            handlebox.xdescent + 0.25 * handlebox.width,
+            handlebox.ydescent + 0.5 * handlebox.height,
+        )
+        radius = handlebox.height
+        patch = mpatches.Circle(
+            xy=center,
+            radius=radius * self.radius_dict[mst] / self.radius_dict[lst],
+            facecolor=self.colors_dict[mst],
+            edgecolor=self.colors_dict[mst],
             transform=handlebox.get_transform(),
         )
         handlebox.add_artist(patch)
         return patch
 
 
-class SSTHandler(object):
+class SSTHandler(TelescopeHandler):
     """
     Legend handler class to plot a representation of an SST in an array layout.
     """
 
-    @staticmethod
-    def legend_artist(legend, orig_handle, fontsize, handlebox):
+    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
         center = (
             handlebox.xdescent + 0.25 * handlebox.width,
             handlebox.ydescent + 0.5 * handlebox.height,
@@ -258,9 +279,29 @@ def legend_artist(legend, orig_handle, fontsize, handlebox):
         radius = handlebox.height
         patch = mpatches.Circle(
             xy=center,
-            radius=radius * (2.8 / 12),
-            facecolor="black",
-            edgecolor="black",
+            radius=radius * self.radius_dict[sst] / self.radius_dict[lst],
+            facecolor=self.colors_dict[sst],
+            edgecolor=self.colors_dict[sst],
+            transform=handlebox.get_transform(),
+        )
+        handlebox.add_artist(patch)
+        return patch
+
+
+class SCTHandler(object):
+    """
+    Legend handler class to plot a representation of an SCT in an array layout.
+    """
+
+    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
+        x0, y0 = handlebox.xdescent + 0.1 * handlebox.width, handlebox.ydescent
+        width = height = handlebox.height
+        patch = mpatches.Rectangle(
+            [x0, y0],
+            width,
+            height,
+            facecolor=self.colors_dict[sct],
+            edgecolor=self.colors_dict[sct],
             transform=handlebox.get_transform(),
         )
         handlebox.add_artist(patch)
@@ -288,3 +329,10 @@ def legend_artist(legend, orig_handle, fontsize, handlebox):
         )
         handlebox.add_artist(patch)
         return patch
+
+
+all_telescope_objects = {lst: LSTObject, mst: MSTObject, sct: SCTObject, sst: SSTObject}
+all_telescope_handlers = {lst: LSTHandler, mst: MSTHandler, sct: SCTHandler, sst: SSTHandler}
+legend_handler_map = {}
+for tel_type in names.all_telescope_class_names:
+    legend_handler_map[all_telescope_objects[tel_type]] = all_telescope_handlers[tel_type]
diff --git a/simtools/visualization/visualize.py b/simtools/visualization/visualize.py
index aeae2b7ba..8fbfa28b9 100644
--- a/simtools/visualization/visualize.py
+++ b/simtools/visualization/visualize.py
@@ -6,17 +6,28 @@
 
 import astropy.units as u
 import matplotlib.gridspec as gridspec
+import matplotlib.patches as mpatches
 import matplotlib.pyplot as plt
+import numpy as np
 from astropy.table import QTable
 from cycler import cycler
+from matplotlib.collections import PatchCollection
+
+from simtools.util import general as gen
+from simtools.util import names
+from simtools.util.names import mst, sct, sst
+from simtools.visualization import legend_handlers as leg_h
 
 __all__ = [
     "get_colors",
     "get_lines",
     "get_markers",
+    "get_telescope_patch",
     "plot_1D",
+    "plot_array",
     "plot_hist_2D",
     "plot_table",
+    "set_style",
 ]
 
 COLORS = dict()
@@ -531,3 +542,146 @@ def plot_hist_2D(data, **kwargs):
     fig.tight_layout()
 
     return fig
+
+
+@u.quantity_input()
+def get_telescope_patch(name, x, y, radius):
+    """
+    Collect the patch of one telescope to be plotted by plot_array.
+
+    Parameters
+    ----------
+    name: str
+        Name of the telescope (type).
+    x: astropy.units.Quantity
+        x position of the telescope usually in meters.
+    y: astropy.units.Quantity
+        y position of the telescope usually in meters.
+    radius: astropy.units.Quantity
+        Radius of the telescope sphere usually in meters.
+
+    Returns
+    -------
+    patch
+        Instance of mpatches.Circle.
+    """
+    tel_obj = leg_h.TelescopeHandler()
+    valid_name = names.get_telescope_type(name)
+    fill_flag = False
+
+    x = x.to(u.m)
+    y = y.to(u.m)
+    radius = radius.to(u.m)
+
+    if valid_name == mst:
+        fill_flag = True
+    if valid_name == sct:
+        patch = mpatches.Rectangle(
+            ((x - radius / 2).value, (y - radius / 2).value),
+            width=radius.value,
+            height=radius.value,
+            fill=False,
+            color=tel_obj.colors_dict[sct],
+        )
+    else:
+        patch = mpatches.Circle(
+            (x.value, y.value),
+            radius=radius.value,
+            fill=fill_flag,
+            color=tel_obj.colors_dict[valid_name],
+        )
+    return patch
+
+
+def plot_array(telescopes, rotate_angle=0 * u.deg):
+    """
+    Plot the array of telescopes.
+    Rotation of the array elements is possible through the 'rotate_angle' given either in degrees,
+    or in radians.
+    The rotation does not change Telescope instance attributes.
+
+    Parameters
+    ----------
+    telescopes: dict
+        Dictionary with the telescope position and names.
+    rotate_angle:
+        Angle to rotate the plot. For rotate_angle = 0 the x-axis points towards the east, and\
+        the y-axis points towards the North.
+
+    Returns
+    -------
+    plt.figure
+        Instance of plt.figure with the array of telescopes plotted.
+
+    """
+
+    fig, ax = plt.subplots(1)
+    legend_objects = []
+    legend_labels = []
+    tel_counters = {one_telescope: 0 for one_telescope in names.all_telescope_class_names}
+    if rotate_angle != 0:
+        pos_x_rotated, pos_y_rotated = gen.rotate(
+            rotate_angle, telescopes["pos_x"], telescopes["pos_y"]
+        )
+    else:
+        pos_x_rotated, pos_y_rotated = telescopes["pos_x"], telescopes["pos_y"]
+
+    size_factor = max(np.max(pos_x_rotated), np.max(pos_y_rotated)) / (300.0 * u.m)
+    fontsize = 12
+
+    patches = []
+    for i_tel, tel_now in enumerate(telescopes):
+        for tel_type in tel_counters:
+            if tel_type in tel_now["telescope_name"]:
+                tel_counters[tel_type] += 1
+        i_tel_name = names.get_telescope_type(telescopes[i_tel]["telescope_name"])
+        if i_tel_name == sst:
+            fontsize = 5
+        patches.append(
+            get_telescope_patch(
+                i_tel_name,
+                pos_x_rotated[i_tel],
+                pos_y_rotated[i_tel],
+                telescopes[i_tel]["radius"] * size_factor,
+            )
+        )
+        ax.text(
+            pos_x_rotated[i_tel].value,
+            pos_y_rotated[i_tel].value + telescopes[i_tel]["radius"].value,
+            telescopes[i_tel]["telescope_name"],
+            horizontalalignment="center",
+            verticalalignment="bottom",
+            fontsize=fontsize,
+        )
+
+    for _, one_telescope in enumerate(names.all_telescope_class_names):
+        if tel_counters[one_telescope] > 0:
+            legend_objects.append(leg_h.all_telescope_objects[one_telescope]())
+            legend_labels.append(f"{one_telescope} ({tel_counters[one_telescope]})")
+
+    plt.gca().add_collection(PatchCollection(patches, match_original=True))
+
+    x_title = "East [m]"
+    y_title = "North [m]"
+    plt.axis("square")
+    plt.grid(True)
+    plt.gca().set_axisbelow(True)
+    plt.xlabel(x_title, fontsize=18, labelpad=0)
+    plt.ylabel(y_title, fontsize=18, labelpad=0)
+    plt.tick_params(axis="both", which="major", labelsize=15)
+
+    legend_handler_map = {
+        list(leg_h.legend_handler_map.keys())[step]: list(leg_h.legend_handler_map.values())[step]()
+        for step, _ in enumerate(leg_h.legend_handler_map)
+    }
+    plt.legend(
+        legend_objects,
+        legend_labels,
+        handler_map=legend_handler_map,
+        prop={"size": 11},
+        loc="best",
+    )
+
+    plt.tight_layout()
+
+    return fig


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index cb7e41e9a..ca82e94a1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,8 +6,10 @@
 import pytest
 
 import simtools.io_handler
+import simtools.util.general as gen
 from simtools import db_handler
 from simtools.configuration.configurator import Configurator
+from simtools.layout.layout_array import LayoutArray
 from simtools.model.telescope_model import TelescopeModel
 
 logger = logging.getLogger()
@@ -186,3 +188,29 @@ def telescope_model_sst(db, db_config, io_handler):
         label="test-telescope-model-sst",
     )
     return telescope_model_SST
+
+
+@pytest.fixture
+def telescope_test_file(db, args_dict, io_handler):
+    test_file_name = "telescope_positions-North-TestLayout.ecsv"
+    db.export_file_db(
+        db_name="test-data",
+        dest=io_handler.get_output_directory(dir_type="model", test=True),
+        file_name=test_file_name,
+    )
+
+    cfg_file = gen.find_file(
+        test_file_name,
+        io_handler.get_output_directory(dir_type="model", test=True),
+    )
+    return cfg_file
+
+
+@pytest.fixture
+def layout_array_north_instance(io_handler, db_config):
+    return LayoutArray(site="North", mongo_db_config=db_config, name="test_layout")
+
+
+@pytest.fixture
+def corsika_telescope_data_dict(layout_array_north_instance):
+    return layout_array_north_instance._from_corsika_file_to_dict()
diff --git a/tests/unit_tests/corsika/test_corsika_config.py b/tests/unit_tests/corsika/test_corsika_config.py
index a6c28cdaf..b163495a4 100644
--- a/tests/unit_tests/corsika/test_corsika_config.py
+++ b/tests/unit_tests/corsika/test_corsika_config.py
@@ -33,9 +33,10 @@ def corsika_config_data():
 
 
 @pytest.fixture
-def corsika_config(io_handler, corsika_config_data):
+def corsika_config(io_handler, db_config, corsika_config_data):
 
     corsika_config = CorsikaConfig(
+        mongo_db_config=db_config,
         site="Paranal",
         layout_name="4LST",
         label="test-corsika-config",
@@ -75,13 +76,14 @@ def test_export_input_file(corsika_config):
     assert input_file.exists()
 
 
-def test_wrong_par_in_config_data(corsika_config_data):
+def test_wrong_par_in_config_data(corsika_config_data, db_config):
 
     logger.info("test_wrong_primary_name")
     new_config_data = copy(corsika_config_data)
     new_config_data["wrong_par"] = 20 * u.m
     with pytest.raises(InvalidCorsikaInput):
         corsika_test_Config = CorsikaConfig(
+            mongo_db_config=db_config,
             site="LaPalma",
             layout_name="1LST",
             label="test-corsika-config",
@@ -90,13 +92,14 @@ def test_wrong_par_in_config_data(corsika_config_data):
         corsika_test_Config.print_user_parameters()
 
 
-def test_units_of_config_data(corsika_config_data):
+def test_units_of_config_data(corsika_config_data, db_config):
 
     logger.info("test_units_of_config_data")
     new_config_data = copy(corsika_config_data)
     new_config_data["zenith"] = 20 * u.m
     with pytest.raises(InvalidCorsikaInput):
         corsika_test_Config = CorsikaConfig(
+            mongo_db_config=db_config,
             site="LaPalma",
             layout_name="1LST",
             label="test-corsika-config",
@@ -105,13 +108,14 @@ def test_units_of_config_data(corsika_config_data):
         corsika_test_Config.print_user_parameters()
 
 
-def test_len_of_config_data(corsika_config_data):
+def test_len_of_config_data(corsika_config_data, db_config):
 
     logger.info("test_len_of_config_data")
     new_config_data = copy(corsika_config_data)
     new_config_data["erange"] = [20 * u.TeV]
     with pytest.raises(InvalidCorsikaInput):
         corsika_test_Config = CorsikaConfig(
+            mongo_db_config=db_config,
             site="LaPalma",
             layout_name="1LST",
             label="test-corsika-config",
@@ -120,13 +124,14 @@ def test_len_of_config_data(corsika_config_data):
         corsika_test_Config.print_user_parameters()
 
 
-def test_wrong_primary_name(corsika_config_data):
+def test_wrong_primary_name(corsika_config_data, db_config):
 
     logger.info("test_wrong_primary_name")
     new_config_data = copy(corsika_config_data)
     new_config_data["primary"] = "rock"
     with pytest.raises(InvalidCorsikaInput):
         corsika_test_Config = CorsikaConfig(
+            mongo_db_config=db_config,
             site="LaPalma",
             layout_name="1LST",
             label="test-corsika-config",
@@ -135,13 +140,14 @@ def test_wrong_primary_name(corsika_config_data):
         corsika_test_Config.print_user_parameters()
 
 
-def test_missing_input(corsika_config_data):
+def test_missing_input(corsika_config_data, db_config):
 
     logger.info("test_missing_input")
     new_config_data = copy(corsika_config_data)
     new_config_data.pop("primary")
     with pytest.raises(MissingRequiredInputInCorsikaConfigData):
         corsika_test_Config = CorsikaConfig(
+            mongo_db_config=db_config,
             site="LaPalma",
             layout_name="1LST",
             label="test-corsika-config",
@@ -160,7 +166,7 @@ def test_set_user_parameters(corsika_config_data, corsika_config):
     assert new_corsika_config.get_user_parameter("thetap") == [0, 0]
 
 
-def test_config_data_from_yaml_file(db, io_handler):
+def test_config_data_from_yaml_file(db, io_handler, db_config):
 
     logger.info("test_config_data_from_yaml_file")
     test_file_name = "corsikaConfigTest.yml"
@@ -174,6 +180,7 @@ def test_config_data_from_yaml_file(db, io_handler):
         test_file_name, io_handler.get_output_directory(dir_type="model", test=True)
     )
     cc = CorsikaConfig(
+        mongo_db_config=db_config,
         site="Paranal",
         layout_name="4LST",
         label="test-corsika-config",
@@ -214,3 +221,40 @@ def test_get_file_name(corsika_config, io_handler):
     )
     with pytest.raises(ValueError):
         corsika_config.get_file_name("foobar")
+
+
+def test_load_corsika_parameters_file(corsika_config, io_handler, caplog):
+    corsika_parameters_file = io_handler.get_input_data_file("parameters", "corsika_parameters.yml")
+    corsika_dict = corsika_config.load_corsika_parameters_file(corsika_parameters_file)
+    assert "Loading CORSIKA parameters from file" in caplog.text
+    sphere_center = {
+        "LST": {"value": 16, "unit": "m"},
+        "MST": {"value": 9, "unit": "m"},
+        "SCT": {"value": 6.1, "unit": "m"},
+        "SST": {"value": 3.25, "unit": "m"},
+    }
+    sphere_radius = {
+        "LST": {"value": 12.5, "unit": "m"},
+        "MST": {"value": 9.15, "unit": "m"},
+        "SCT": {"value": 7.15, "unit": "m"},
+        "SST": {"value": 3, "unit": "m"},
+    }
+    assert isinstance(corsika_dict, dict)
+    for tel_type in sphere_center:
+        assert (
+            sphere_center[tel_type]["value"]
+            == corsika_dict["corsika_sphere_center"][tel_type]["value"]
+        )
+        assert (
+            sphere_center[tel_type]["unit"]
+            == corsika_dict["corsika_sphere_center"][tel_type]["unit"]
+        )
+    for tel_type in sphere_radius:
+        assert (
+            sphere_radius[tel_type]["value"]
+            == corsika_dict["corsika_sphere_radius"][tel_type]["value"]
+        )
+        assert (
+            sphere_radius[tel_type]["unit"]
+            == corsika_dict["corsika_sphere_radius"][tel_type]["unit"]
+        )
diff --git a/tests/unit_tests/corsika/test_corsika_runner.py b/tests/unit_tests/corsika/test_corsika_runner.py
index e2b1848a0..fd6b3d3ef 100644
--- a/tests/unit_tests/corsika/test_corsika_runner.py
+++ b/tests/unit_tests/corsika/test_corsika_runner.py
@@ -29,9 +29,10 @@ def corsika_config_data():
 
 
 @pytest.fixture
-def corsika_runner(corsika_config_data, io_handler, simtel_path):
+def corsika_runner(corsika_config_data, io_handler, simtel_path, db_config):
 
     corsika_runner = CorsikaRunner(
+        mongo_db_config=db_config,
         site="south",
         layout_name="test-layout",
         simtel_source_path=simtel_path,
diff --git a/tests/unit_tests/layout/test_layout_array.py b/tests/unit_tests/layout/test_layout_array.py
index 2faa94a6b..262537c24 100644
--- a/tests/unit_tests/layout/test_layout_array.py
+++ b/tests/unit_tests/layout/test_layout_array.py
@@ -25,15 +25,6 @@ def layout_center_data_dict():
     }
 
 
-@pytest.fixture
-def corsika_telescope_data_dict():
-    return {
-        "corsika_sphere_radius": {"LST": 12.5 * u.m, "MST": 9.15 * u.m, "SST": 3.0 * u.m},
-        "corsika_sphere_center": {"LST": 16 * u.m, "MST": 9 * u.m, "SST": 3.25 * u.m},
-        "corsika_obs_level": 2158 * u.m,
-    }
-
-
 @pytest.fixture
 def telescope_test_file(db, args_dict, io_handler):
     test_file_name = "telescope_positions-North-TestLayout.ecsv"
@@ -50,79 +41,135 @@ def telescope_test_file(db, args_dict, io_handler):
     return cfg_file
 
 
-def test_from_layout_array_name(io_handler):
+@pytest.fixture
+def layout_array_north_four_LST_instance(
+    layout_center_data_dict, corsika_telescope_data_dict, io_handler, db_config
+):
+    layout = LayoutArray(
+        site="North",
+        mongo_db_config=db_config,
+        label="test_layout",
+        name="LST4",
+        layout_center_data=layout_center_data_dict,
+        corsika_telescope_data=corsika_telescope_data_dict,
+    )
+    return layout
+
 
-    layout = LayoutArray.from_layout_array_name("south-TestLayout")
+@pytest.fixture
+def manual_corsika_dict_north():
+    return {
+        "corsika_sphere_radius": {
+            "LST": 12.5 * u.m,
+            "MST": 9.15 * u.m,
+            "SCT": 7.15 * u.m,
+            "SST": 3 * u.m,
+        },
+        "corsika_sphere_center": {
+            "LST": 16 * u.m,
+            "MST": 9 * u.m,
+            "SCT": 6.1 * u.m,
+            "SST": 3.25 * u.m,
+        },
+        "corsika_obs_level": 2158 * u.m,
+    }
+
+
+def test_from_layout_array_name(io_handler, db_config):
+
+    layout = LayoutArray.from_layout_array_name(
+        mongo_db_config=db_config, layout_array_name="south-TestLayout"
+    )
 
     assert 99 == layout.get_number_of_telescopes()
 
 
-def test_initialize_coordinate_systems(layout_center_data_dict):
+def test_initialize_coordinate_systems(layout_center_data_dict, layout_array_north_instance):
 
-    layout = LayoutArray(name="test_layout")
-    layout._initialize_coordinate_systems()
-    _x, _y, _z = layout._array_center.get_coordinates("corsika")
+    layout_array_north_instance._initialize_coordinate_systems()
+    _x, _y, _z = layout_array_north_instance._array_center.get_coordinates("corsika")
     assert _x == 0.0 * u.m and _y == 0.0 * u.m and _z == 0.0 * u.m
-    _lat, _lon, _z = layout._array_center.get_coordinates("mercator")
+    _lat, _lon, _z = layout_array_north_instance._array_center.get_coordinates("mercator")
     assert np.isnan(_lat) and np.isnan(_lon)
 
-    layout._initialize_coordinate_systems(layout_center_data_dict)
-    _x, _y, _z = layout._array_center.get_coordinates("corsika")
+    layout_array_north_instance._initialize_coordinate_systems(layout_center_data_dict)
+    _x, _y, _z = layout_array_north_instance._array_center.get_coordinates("corsika")
     assert _x == 0.0 * u.m and _y == 0.0 * u.m and _z == layout_center_data_dict["center_alt"]
-    _lat, _lon, _z = layout._array_center.get_coordinates("mercator")
+    _lat, _lon, _z = layout_array_north_instance._array_center.get_coordinates("mercator")
     assert _lat.value == pytest.approx(layout_center_data_dict["center_lat"].value, 1.0e-2)
     assert _lon.value == pytest.approx(layout_center_data_dict["center_lon"].value, 1.0e-2)
-    _E, _N, _z = layout._array_center.get_coordinates("utm")
+    _E, _N, _z = layout_array_north_instance._array_center.get_coordinates("utm")
     assert _E.value == pytest.approx(217609.0, 1.0)
     assert _N.value == pytest.approx(3185067.0, 1.0)
 
 
-def test_initialize_corsika_telescope_from_file(corsika_telescope_data_dict, args_dict, io_handler):
+def test_initialize_corsika_telescope_from_file(
+    corsika_telescope_data_dict, args_dict, io_handler, layout_array_north_instance
+):
 
-    layout = LayoutArray(name="test_layout")
-    layout._initialize_corsika_telescope()
+    layout_array_north_instance._initialize_corsika_telescope()
 
     for key, value in corsika_telescope_data_dict["corsika_sphere_radius"].items():
-        assert value == layout._corsika_telescope["corsika_sphere_radius"][key]
+        assert value == layout_array_north_instance._corsika_telescope["corsika_sphere_radius"][key]
     for key, value in corsika_telescope_data_dict["corsika_sphere_center"].items():
-        assert value == layout._corsika_telescope["corsika_sphere_center"][key]
-
-
-def test_read_tel_list(telescope_test_file):
+        assert value == layout_array_north_instance._corsika_telescope["corsika_sphere_center"][key]
+
+
+def test_read_telescope_list_file(telescope_test_file, layout_array_north_instance, db_config):
+    table = layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    assert table.meta["data_type"] == "positionfile"
+    assert table.meta["description"] == "telescope positions for CTA North (La Palma)"
+    columns = ["pos_x", "pos_y", "pos_z"]
+    pos_x = [-70.93, -35.27, 75.28, 30.91, -211.54, -153.26]
+    pos_y = [-52.07, 66.14, 50.49, -64.54, 5.66, 169.01]
+    pos_z = [43.00, 32.00, 28.70, 32.00, 50.3, 24.0]
+    columns_manual = [pos_x, pos_y, pos_z]
+    for step, col in enumerate(columns):
+        assert all(
+            [
+                pytest.approx(table[col][tel_num].value, 0.1) == columns_manual[step][tel_num]
+                for tel_num in range(6)
+            ]
+        )
+
+
+def test_initialize_layout_array_from_telescope_file(
+    telescope_test_file, layout_array_north_instance, db_config
+):
 
-    layout = LayoutArray(name="test_layout")
-    layout.read_telescope_list_file(telescope_test_file)
-    layout.convert_coordinates()
-    assert 19 == layout.get_number_of_telescopes()
+    layout_array_north_instance.initialize_layout_array_from_telescope_file(telescope_test_file)
+    layout_array_north_instance.convert_coordinates()
+    assert 19 == layout_array_north_instance.get_number_of_telescopes()
 
-    layout_2 = LayoutArray(name="test_layout", telescope_list_file=telescope_test_file)
+    layout_2 = LayoutArray(
+        name="test_layout",
+        telescope_list_file=telescope_test_file,
+    )
     layout_2.convert_coordinates()
     assert 19 == layout_2.get_number_of_telescopes()
 
 
-def test_add_tel(telescope_test_file):
+def test_add_tel(telescope_test_file, layout_array_north_instance):
 
-    layout = LayoutArray(name="test_layout")
-    layout.read_telescope_list_file(telescope_test_file)
-    ntel_before = layout.get_number_of_telescopes()
-    layout.add_telescope("LST-05", "corsika", 100.0 * u.m, 50.0 * u.m, 2177.0 * u.m)
-    ntel_after = layout.get_number_of_telescopes()
+    layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    ntel_before = layout_array_north_instance.get_number_of_telescopes()
+    layout_array_north_instance.add_telescope(
+        "LST-05", "corsika", 100.0 * u.m, 50.0 * u.m, 2177.0 * u.m
+    )
+    ntel_after = layout_array_north_instance.get_number_of_telescopes()
     assert ntel_before + 1 == ntel_after
 
-    layout.add_telescope("LST-05", "corsika", 100.0 * u.m, 50.0 * u.m, None, 50.0 * u.m)
-    assert layout._telescope_list[-1].get_altitude().value == pytest.approx(2192.0)
+    layout_array_north_instance.add_telescope(
+        "LST-05", "corsika", 100.0 * u.m, 50.0 * u.m, None, 50.0 * u.m
+    )
+    assert layout_array_north_instance._telescope_list[-1].get_altitude().value == pytest.approx(
+        2192.0
+    )
 
 
-def test_build_layout(
-    layout_center_data_dict, corsika_telescope_data_dict, tmp_test_directory, io_handler
-):
+def test_build_layout(layout_array_north_four_LST_instance, tmp_test_directory, db_config):
 
-    layout = LayoutArray(
-        label="test_layout",
-        name="LST4",
-        layout_center_data=layout_center_data_dict,
-        corsika_telescope_data=corsika_telescope_data_dict,
-    )
+    layout = layout_array_north_four_LST_instance
 
     layout.add_telescope(
         telescope_name="LST-01",
@@ -158,8 +205,7 @@ def test_build_layout(
     layout.export_telescope_list(crs_name="corsika")
 
     # Building a second layout from the file exported by the first one
-    layout_2 = LayoutArray("test_layout_2")
-    layout_2.read_telescope_list_file(layout.telescope_list_file)
+    layout_2 = LayoutArray(telescope_list_file=layout.telescope_list_file, name="test_layout_2")
 
     assert 4 == layout_2.get_number_of_telescopes()
     assert layout_2._array_center.get_altitude().value == pytest.approx(
@@ -167,14 +213,9 @@ def test_build_layout(
     )
 
 
-def test_converting_center_coordinates(layout_center_data_dict, corsika_telescope_data_dict):
+def test_converting_center_coordinates(layout_array_north_four_LST_instance):
 
-    layout = LayoutArray(
-        label="test_layout",
-        name="LST4",
-        layout_center_data=layout_center_data_dict,
-        corsika_telescope_data=corsika_telescope_data_dict,
-    )
+    layout = layout_array_north_four_LST_instance
 
     _lat, _lon, _ = layout._array_center.get_coordinates("mercator")
     assert _lat.value == pytest.approx(28.7621661)
@@ -187,16 +228,9 @@ def test_converting_center_coordinates(layout_center_data_dict, corsika_telescop
     assert layout._array_center.get_altitude().value == pytest.approx(2177.0)
 
 
-def test_get_corsika_input_list(
-    layout_center_data_dict, corsika_telescope_data_dict, telescope_test_file
-):
+def test_get_corsika_input_list(layout_array_north_four_LST_instance):
 
-    layout = LayoutArray(
-        label="test_layout",
-        name="LST4",
-        layout_center_data=layout_center_data_dict,
-        corsika_telescope_data=corsika_telescope_data_dict,
-    )
+    layout = layout_array_north_four_LST_instance
     layout.add_telescope(
         telescope_name="LST-01",
         crs_name="corsika",
@@ -209,14 +243,9 @@ def test_get_corsika_input_list(
     assert corsika_input_list == "TELESCOPE\t 57.500E2\t 57.500E2\t 0.000E2\t 12.500E2\t # LST-01\n"
 
 
-def test_altitude_from_corsika_z(layout_center_data_dict, corsika_telescope_data_dict):
+def test_altitude_from_corsika_z(layout_array_north_four_LST_instance):
 
-    layout = LayoutArray(
-        label="test_layout",
-        name="LST4",
-        layout_center_data=layout_center_data_dict,
-        corsika_telescope_data=corsika_telescope_data_dict,
-    )
+    layout = layout_array_north_four_LST_instance
 
     layout.add_telescope(
         telescope_name="LST-01",
@@ -232,3 +261,104 @@ def test_altitude_from_corsika_z(layout_center_data_dict, corsika_telescope_data
     )
     with pytest.raises(TypeError):
         layout._altitude_from_corsika_z(5.0, None, "LST-01")
+
+
+def test_include_radius_into_telescope_table(layout_array_north_instance, telescope_test_file):
+    telescope_table = layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    telescope_table_with_radius = layout_array_north_instance.include_radius_into_telescope_table(
+        telescope_table
+    )
+    values_from_file = [20.190000534057617, -352.4599914550781, 62.29999923706055, 9.6]
+    keys = ["pos_x", "pos_y", "pos_z", "radius"]
+    mst_10_index = telescope_table_with_radius["telescope_name"] == "MST-10"
+    for key, value_manual in zip(keys, values_from_file):
+        assert (
+            pytest.approx(telescope_table_with_radius[mst_10_index][key].value[0], 1e-2)
+            == value_manual
+        )
+
+
+def test_from_corsika_file_to_dict(
+    layout_array_north_instance, manual_corsika_dict_north, db, io_handler
+):
+    def run(corsika_dict):
+        for key, value in corsika_dict.items():
+            if isinstance(value, dict):
+                for tel_type, subvalue in value.items():
+                    assert subvalue == manual_corsika_dict_north[key][tel_type]
+            else:
+                assert value == manual_corsika_dict_north[key]
+
+    corsika_dict = layout_array_north_instance._from_corsika_file_to_dict()
+    run(corsika_dict)
+
+    test_file_name = "corsika_parameters_2.yml"
+    db.export_file_db(
+        db_name="test-data",
+        dest=io_handler.get_output_directory(dir_type="parameters", test=True),
+        file_name=test_file_name,
+    )
+
+    corsika_config_file = gen.find_file(
+        test_file_name, io_handler.get_output_directory(dir_type="parameters", test=True)
+    )
+    corsika_dict = layout_array_north_instance._from_corsika_file_to_dict(
+        file_name=corsika_config_file
+    )
+    run(corsika_dict)
+
+    with pytest.raises(FileNotFoundError):
+        corsika_dict = layout_array_north_instance._from_corsika_file_to_dict(
+            file_name="file_doesnt_exist.yml"
+        )
+
+
+def test_initialize_corsika_telescope_from_dict(
+    layout_array_north_instance, manual_corsika_dict_north
+):
+    layout_array_north_instance._initialize_corsika_telescope_from_dict(manual_corsika_dict_north)
+    for key, value in layout_array_north_instance._corsika_telescope.items():
+        if isinstance(value, dict):
+            for tel_type, subvalue in value.items():
+                assert subvalue == manual_corsika_dict_north[key][tel_type]
+        else:
+            assert value == manual_corsika_dict_north[key]
+
+
+def test_assign_unit_to_quantity(layout_array_north_instance):
+    quantity = layout_array_north_instance._assign_unit_to_quantity(10, u.m)
+    assert quantity == 10 * u.m
+
+    quantity = layout_array_north_instance._assign_unit_to_quantity(1000 * u.cm, u.m)
+    assert quantity == 10 * u.m
+
+    with pytest.raises(u.UnitConversionError):
+        layout_array_north_instance._assign_unit_to_quantity(1000 * u.TeV, u.m)
+
+
+def test_try_set_altitude(layout_array_north_instance, telescope_test_file):
+    table = layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    obs_level = 2158.0
+    manual_z_positions = [43.00, 32.00, 28.70, 32.00, 50.3, 24.0]
+    corsika_sphere_center = [16.0, 16.0, 16.0, 16.0, 9.0, 9.0]
+    manual_altitudes = [
+        manual_z_positions[step] + obs_level - corsika_sphere_center[step] for step in range(6)
+    ]
+    for step, row in enumerate(table[:6]):
+        tel = layout_array_north_instance._load_telescope_names(row)
+        layout_array_north_instance._try_set_altitude(row, tel, table)
+        for _crs in tel.crs.values():
+            assert pytest.approx(_crs["zz"]["value"], 0.1) == manual_altitudes[step]
+
+
+def test_try_set_coordinate(layout_array_north_instance, telescope_test_file):
+    table = layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    manual_xx = [-70.93, -35.27, 75.28, 30.91, -211.54, -153.26]
+    manual_yy = [-52.07, 66.14, 50.49, -64.54, 5.66, 169.01]
+    for step, row in enumerate(table[:6]):
+        tel = layout_array_north_instance._load_telescope_names(row)
+        layout_array_north_instance._try_set_coordinate(
+            row, tel, table, "corsika", "pos_x", "pos_y"
+        )
+        assert pytest.approx(tel.crs["corsika"]["xx"]["value"], 0.1) == manual_xx[step]
+        assert pytest.approx(tel.crs["corsika"]["yy"]["value"], 0.1) == manual_yy[step]
diff --git a/tests/unit_tests/simtel/test_simtel_config_writer.py b/tests/unit_tests/simtel/test_simtel_config_writer.py
index 0d18f9e12..895ff839a 100644
--- a/tests/unit_tests/simtel/test_simtel_config_writer.py
+++ b/tests/unit_tests/simtel/test_simtel_config_writer.py
@@ -25,8 +25,10 @@ def simtel_config_writer():
 
 
 @pytest.fixture
-def layout(io_handler):
-    layout = LayoutArray.from_layout_array_name("South-4LST")
+def layout(io_handler, db_config):
+    layout = LayoutArray.from_layout_array_name(
+        mongo_db_config=db_config, layout_array_name="South-4LST"
+    )
     return layout
 
 
diff --git a/tests/unit_tests/test_simulator.py b/tests/unit_tests/test_simulator.py
index 8ce82afe3..64b73d3d1 100644
--- a/tests/unit_tests/test_simulator.py
+++ b/tests/unit_tests/test_simulator.py
@@ -83,13 +83,14 @@ def array_simulator(label, array_config_data, io_handler, db_config, simtel_path
 
 
 @pytest.fixture
-def shower_simulator(label, shower_config_data, io_handler, simtel_path):
+def shower_simulator(label, shower_config_data, io_handler, db_config, simtel_path):
 
     shower_simulator = Simulator(
         label=label,
         simulator="corsika",
         simulator_source_path=simtel_path,
         config_data=shower_config_data,
+        mongo_db_config=db_config,
     )
     return shower_simulator
 
@@ -311,7 +312,7 @@ def test_get_list_of_files(shower_simulator):
     assert len(shower_simulator.get_list_of_output_files(run_range=[1, 4])) == 14
 
 
-def test_no_corsika_data(shower_config_data, label, simtel_path, io_handler):
+def test_no_corsika_data(shower_config_data, label, simtel_path, io_handler, db_config):
 
     new_shower_config_data = copy(shower_config_data)
     new_shower_config_data.pop("data_directory", None)
@@ -320,6 +321,7 @@ def test_no_corsika_data(shower_config_data, label, simtel_path, io_handler):
         simulator="corsika",
         config_data=new_shower_config_data,
         simulator_source_path=simtel_path,
+        mongo_db_config=db_config,
     )
     files = new_shower_simulator.get_list_of_output_files(run_list=[3])
 
@@ -335,13 +337,14 @@ def test_make_resources_report(array_simulator, input_file_list):
         array_simulator._make_resources_report(input_file_list)
 
 
-def test_get_runs_to_simulate(shower_config_data, simtel_path, io_handler):
+def test_get_runs_to_simulate(shower_config_data, simtel_path, io_handler, db_config):
 
     shower_simulator = Simulator(
         label="corsika-test",
         simulator="corsika",
         config_data=shower_config_data,
         simulator_source_path=simtel_path,
+        mongo_db_config=db_config,
     )
     assert len(shower_simulator.runs) == len(
         shower_simulator._get_runs_to_simulate(run_list=None, run_range=None)
diff --git a/tests/unit_tests/utils/test_general.py b/tests/unit_tests/utils/test_general.py
index fe4d8f346..bc92c8cc9 100644
--- a/tests/unit_tests/utils/test_general.py
+++ b/tests/unit_tests/utils/test_general.py
@@ -5,8 +5,10 @@
 from pathlib import Path
 
 import astropy.units as u
+import numpy as np
 import pytest
 import yaml
+from astropy.coordinates.errors import UnitsError
 
 import simtools.util.general as gen
 from simtools.util.general import InvalidConfigEntry
@@ -162,3 +164,45 @@ def test_change_dict_keys_case():
 
     _changed_to_upper = gen.change_dict_keys_case(copy(_lower_dict), False)
     assert _changed_to_upper == _upper_dict
+
+
+def test_rotate_telescope_position():
+    x = np.array([-10, -10, 10, 10]).astype(int)
+    y = np.array([-10.0, 10.0, -10.0, 10.0]).astype(float)
+    angle_deg = 30 * u.deg
+    x_rot_manual = np.array([-13.7, -3.7, 3.7, 13.7])
+    y_rot_manual = np.array([-3.7, 13.7, -13.7, 3.7])
+
+    def check_results(x_to_test, y_to_test, x_right, y_right, angle):
+        x_rot, y_rot = gen.rotate(angle, x_to_test, y_to_test)
+        x_rot, y_rot = np.around(x_rot, 1), np.around(y_rot, 1)
+        for element, _ in enumerate(x):
+            assert x_right[element] == x_rot[element]
+            assert y_right[element] == y_rot[element]
+
+    # Testing without units
+    check_results(x, y, x_rot_manual, y_rot_manual, angle_deg)
+
+    x_new_array, y_new_array = x * u.m, y * u.m
+    x_rot_new_array, y_rot_new_array = x_rot_manual * u.m, y_rot_manual * u.m
+
+    # Testing with units
+    check_results(x_new_array, y_new_array, x_rot_new_array, y_rot_new_array, angle_deg)
+
+    # Testing with radians
+    check_results(x_new_array, y_new_array, x_rot_new_array, y_rot_new_array, angle_deg.to(u.rad))
+
+    with pytest.raises(TypeError):
+        gen.rotate(angle_deg, x, y[0])
+    with pytest.raises(TypeError):
+        gen.rotate(angle_deg, str(x[0]), y[0])
+    with pytest.raises(TypeError):
+        gen.rotate(angle_deg, u.Quantity(10), 10)
+    with pytest.raises(TypeError):
+        gen.rotate(angle_deg, x[0], str(y[0]))
+    with pytest.raises(RuntimeError):
+        gen.rotate(angle_deg, x[:-1], y)
+    with pytest.raises(UnitsError):
+        gen.rotate(angle_deg, x_new_array.to(u.cm), y_new_array)
+    with pytest.raises(u.UnitConversionError):
+        gen.rotate(30 * u.m, x_new_array, y_new_array)
diff --git a/tests/unit_tests/utils/test_names.py b/tests/unit_tests/utils/test_names.py
index 5d75e57ff..16a65a904 100644
--- a/tests/unit_tests/utils/test_names.py
+++ b/tests/unit_tests/utils/test_names.py
@@ -64,3 +64,17 @@ def test_simtools_instrument_name():
 
     with pytest.raises(ValueError):
         names.simtools_instrument_name("West", "MST", "FlashCam", "D")
+
+
+def test_translate_corsika_to_simtools():
+    corsika_pars = ["OBSLEV", "corsika_sphere_radius", "corsika_sphere_center"]
+    simtools_pars = ["corsika_obs_level", "corsika_sphere_radius", "corsika_sphere_center"]
+    for step, corsika_par in enumerate(corsika_pars):
+        assert names.translate_corsika_to_simtools(corsika_par) == simtools_pars[step]
+
+
+def test_translate_simtools_to_corsika():
+    corsika_pars = ["OBSLEV", "corsika_sphere_radius", "corsika_sphere_center"]
+    simtools_pars = ["corsika_obs_level", "corsika_sphere_radius", "corsika_sphere_center"]
+    for step, simtools_par in enumerate(simtools_pars):
+        assert names.translate_simtools_to_corsika(simtools_par) == corsika_pars[step]
diff --git a/tests/unit_tests/visualization/test_legend_handlers.py b/tests/unit_tests/visualization/test_legend_handlers.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/unit_tests/test_visualize.py b/tests/unit_tests/visualization/test_visualize.py
similarity index 69%
rename from tests/unit_tests/test_visualize.py
rename to tests/unit_tests/visualization/test_visualize.py
index 3c32f97e1..861ac8129 100644
--- a/tests/unit_tests/test_visualize.py
+++ b/tests/unit_tests/visualization/test_visualize.py
@@ -4,6 +4,8 @@
 
 import astropy.io.ascii
 import astropy.units as u
+import matplotlib.patches as mpatches
+import matplotlib.pyplot as plt
 import numpy as np
 
 import simtools.util.general as gen
@@ -65,11 +67,11 @@ def test_plot_table(db, io_handler):
     test_file_name = "Transmission_Spectrum_PlexiGlass.dat"
     db.export_file_db(
         db_name="test-data",
-        dest=io_handler.get_output_directory(dir_type="model", test=True),
+        dest=io_handler.get_output_directory(dir_type="../model", test=True),
         file_name=test_file_name,
     )
     table_file = gen.find_file(
-        test_file_name, io_handler.get_output_directory(dir_type="model", test=True)
+        test_file_name, io_handler.get_output_directory(dir_type="../model", test=True)
     )
     table = astropy.io.ascii.read(table_file)
 
@@ -91,3 +93,26 @@ def test_add_unit():
     assert visualize._add_unit("Wavelength", value_with_unit) == "Wavelength [nm]"
     value_without_unit = [30, 40]
     assert visualize._add_unit("Wavelength", value_without_unit) == "Wavelength"
+
+
+def test_get_telescope_patch(corsika_telescope_data_dict):
+
+    for tel_type in np.array(list(corsika_telescope_data_dict["corsika_sphere_radius"].keys())):
+        radius = corsika_telescope_data_dict["corsika_sphere_radius"][tel_type].value
+        patch = visualize.get_telescope_patch(tel_type, 0 * u.m, 0 * u.m, radius * u.m)
+        if mpatches.Circle == type(patch):
+            assert (
+                patch.radius == corsika_telescope_data_dict["corsika_sphere_radius"][tel_type].value
+            )
+
+        else:
+            assert isinstance(patch, mpatches.Rectangle)
+
+
+def test_plot_array(telescope_test_file, layout_array_north_instance):
+    telescope_table = layout_array_north_instance.read_telescope_list_file(telescope_test_file)
+    telescopes_dict = layout_array_north_instance.include_radius_into_telescope_table(
+        telescope_table
+    )
+    fig_out = visualize.plot_array(telescopes_dict, rotate_angle=0 * u.deg)
+    assert isinstance(fig_out, type(plt.figure()))