public static Task Execute(TreeHeightCommand command, ILogger logger)
        {
            if (File.Exists(command.Input) == false)
            {
                logger.Fatal("Provided cloud point data file does not exist!");
                Environment.Exit(0);
            }

            using var streamReader = FileFormatDetector.GetCloudStreamReader(command.Input);
            if (streamReader == null)
            {
                logger.Fatal("Not supported data format.");
                Environment.Exit(0);
            }

            var cloud         = new Cloud(streamReader);
            var treeHeightMap = new TreeHeightMap(cloud, command.Resolution, command.MaxHeight);

            treeHeightMap.Export(command.Output);

            return(Task.CompletedTask);
        }
        public static Task Execute(ApproximateCommand command, ILogger logger)
        {
            if (Directory.Exists(command.Input) == false)
            {
                logger.Fatal("Input path does not exist!");
                Environment.Exit(0);
            }

            var inputFiles = Directory.GetFiles(command.Input).Where(p => p.EndsWith(".gpd")).ToList();

            if (inputFiles.Count == 0)
            {
                logger.Fatal("Specified directory does not contain GPD files");
                Environment.Exit(0);
            }

            if (File.Exists(command.TreeHeightMapPath) == false)
            {
                logger.Fatal("Tree heightmap file does not exist!");
                Environment.Exit(0);
            }

            var treeHeightMap = TreeHeightMap.Import(command.TreeHeightMapPath);

            if (treeHeightMap is null)
            {
                logger.Fatal("Could not load tree heightmap! Check if you are providing correct data format");
            }

            var configurationFileContent = File.ReadAllText(command.ConfigurationFile);
            var configuration            = JsonConvert.DeserializeObject <ApproximationConfiguration>(configurationFileContent);

            if (configuration == null)
            {
                logger.Fatal("Could not parse Configuration file!");
                Environment.Exit(0);
            }


            var geneticEllipseMatch = PrepareGeneticEllipseMatchAlgorithm(configuration.EllipsisMatchConfiguration, logger);
            var approximation       =
                new TreeApproximation(geneticEllipseMatch, configuration.EccentricityThreshold, configuration.FitnessThreshold);
            var trees = ParseTrees(inputFiles);

            var treeId = 0;

            foreach (var detectedTree in trees)
            {
                if (detectedTree is null)
                {
                    continue;
                }
                var tree = approximation.ApproximateTree(detectedTree,
                                                         treeHeightMap.GetHeight(detectedTree.Root.Center),
                                                         (float)command.NodeHeight,
                                                         command.Smooth
                                                         );
                if (tree is null)
                {
                    logger.Error("Provided configuration was too restrictive. Could not find sufficient approximations.");
                    Environment.Exit(0);
                }
                if (command.ExportPreview)
                {
                    ExportPreview(command, tree, $"T{treeId}.e.xyz");
                }

                var nodes    = tree.GetAllNodesAsVector();
                var treeInfo = new TreeInfo
                {
                    Height = tree.GetHighestNode().Center.Z - tree.Root.Center.Z
                };
                var nodeDictionary = nodes
                                     .Select((node, id) => new { node, id })
                                     .ToDictionary(value => value.node, value => value.id);
                foreach (var entry in nodeDictionary)
                {
                    var node = entry.Key;
                    treeInfo.SliceInfos.Add(new TreeSliceInfo
                    {
                        Center = node.Center,
                        EllipseEccentricity  = node.Ellipse?.Eccentricity,
                        EllipseFocis         = node.Ellipse is null ? null : new[] { node.Ellipse.FirstFocal, node.Ellipse.SecondFocal },
                        EllipseMajorSemiAxis = node.Ellipse?.MajorRadius,
                        Id       = entry.Value.ToString(),
                        ParentId = node.Parent is null ? null : nodeDictionary[node.Parent].ToString()
                    });