private Vector4 ParseColor(string rgbCommaSeparated)
        {
            if (string.IsNullOrEmpty(rgbCommaSeparated) || rgbCommaSeparated == "std")
            {
                return(VectorsExtensions.CreateColor(255, 255, 255));
            }

            var slots = rgbCommaSeparated.Split(',')
                        .Select(s => byte.Parse(s))
                        .ToArray();

            return(VectorsExtensions.CreateColor(slots[0], slots[1], slots[2]));
        }
Esempio n. 2
0
        internal void ExportVisualTopoToGLB(VisualTopoModel visualTopoModel, DEMDataSet dataset, string outputFile, bool drawOnTexture, float marginMeters, float zFactor, ImageryProvider imageryProvider)
        {
            int   outputSrid       = 3857;
            float lineWidth        = 1.0F;
            int   numTilesPerImage = 4;


            BoundingBox bbox = visualTopoModel.BoundingBox                                                                                                      // relative coords
                               .Translate(visualTopoModel.EntryPoint.Longitude, visualTopoModel.EntryPoint.Latitude, visualTopoModel.EntryPoint.Elevation ?? 0) // absolute coords
                               .Pad(marginMeters)                                                                                                               // margin around model
                               .ReprojectTo(visualTopoModel.SRID, dataset.SRID);                                                                                // DEM coords

            //=======================
            // Height map (get dem elevation for bbox)
            //
            // Get height map
            // Note that ref Bbox means that the bbox will be adjusted to match DEM data
            var heightMap        = elevationService.GetHeightMap(ref bbox, dataset, downloadMissingFiles: true);
            var bboxTerrainSpace = bbox.ReprojectTo(dataset.SRID, outputSrid); // terrain coords

            // Model origin
            GeoPoint axisOriginWorldSpace = visualTopoModel.EntryPoint.ReprojectTo(visualTopoModel.SRID, outputSrid)
                                            .CenterOnOrigin(bboxTerrainSpace);
            Vector3 axisOriginModelSpace = visualTopoModel.EntryPoint.AsVector3();

            //await signalR.ReportGenerateProgressAsync(signalRConnectionId, "Getting GPX extent elevation...", 20);
            //=======================
            // Local transform function from model coordinates (relative to entry, in meters)
            // and global coordinates absolute in final 3D model space
            //
            IEnumerable <GeoPoint> TransformLine(IEnumerable <GeoPoint> line)
            {
                var newLine = line.Translate(visualTopoModel.EntryPoint)     // Translate to entry (=> global topo coord space)
                              .ReprojectTo(visualTopoModel.SRID, outputSrid) // Reproject to terrain coord space
                              .CenterOnOrigin(bboxTerrainSpace)              // Center on terrain space origin
                              .CenterOnOrigin(axisOriginWorldSpace);

                return(newLine);
            };

            //=======================
            // 3D model
            //
            var gltfModel = sharpGltfService.CreateNewModel();

            // Add X/Y/Z axis on entry point
            var axis = meshService.CreateAxis();

            sharpGltfService.AddMesh(gltfModel, "Axis", axis, doubleSided: false);

            int i = 0;

            var triangulation = visualTopoModel.TriangulationFull3D.Clone()
                                .Translate(axisOriginModelSpace)             // already zScaled if zFactor > 1
                                .ReprojectTo(visualTopoModel.SRID, outputSrid)
                                .CenterOnOrigin(bboxTerrainSpace)
                                .CenterOnOrigin(axisOriginWorldSpace.AsVector3());

            gltfModel = sharpGltfService.AddMesh(gltfModel, "Cavite3D", triangulation, VectorsExtensions.CreateColor(0, 255, 0), doubleSided: false);

            ////=======================
            //// 3D cavity model
            //foreach (var line in visualTopoModel.Topology3D) // model.Topology3D is the graph of topo paths
            //{
            //    // Add line to model
            //    gltfModel = sharpGltfService.AddLine(gltfModel
            //                                    , string.Concat("CavitySection", i++)     // name of 3D node
            //                                    , TransformLine(line)               // call transform function
            //                                    , color: VectorsExtensions.CreateColor(255, 0, 0, 128)
            //                                    , lineWidth);
            //}

            // Reproject and center height map coordinates
            heightMap = heightMap.ReprojectTo(dataset.SRID, outputSrid)
                        .CenterOnOrigin(bboxTerrainSpace)
                        .ZScale(zFactor)
                        .CenterOnOrigin(axisOriginWorldSpace);

            //=======================
            // Textures
            //
            PBRTexture pbrTexture = null;

            if (imageryProvider != null)
            {
                TileRange tiles    = imageryService.DownloadTiles(bbox, imageryProvider, numTilesPerImage);
                string    fileName = Path.Combine(Path.GetDirectoryName(outputFile), "Texture.jpg");

                Console.WriteLine("Construct texture...");
                TextureInfo texInfo = null;
                if (drawOnTexture)
                {
                    var topoTexture = visualTopoModel.Topology3D.SelectMany(l => l).Translate(visualTopoModel.EntryPoint).ReprojectTo(visualTopoModel.SRID, 4326);
                    texInfo = imageryService.ConstructTextureWithGpxTrack(tiles, bbox, fileName, TextureImageFormat.image_jpeg
                                                                          , topoTexture, drawGpxVertices: true);
                }
                else
                {
                    texInfo = imageryService.ConstructTexture(tiles, bbox, fileName, TextureImageFormat.image_jpeg);
                }



                pbrTexture = PBRTexture.Create(texInfo, null);
            }
            //
            //=======================

            gltfModel = sharpGltfService.AddTerrainMesh(gltfModel, heightMap, pbrTexture);
            gltfModel.SaveGLB(outputFile);
        }
Esempio n. 3
0
        protected override ModelRoot AddToModel(ModelRoot gltfModel, string nodeName, IEnumerable <RailwayModel> models)
        {
            gltfModel = _gltfService.AddLines(gltfModel, glTFNodeName, models.Select(m => (m.LineString.AsEnumerable(), WidthMeters)), VectorsExtensions.CreateColor(165, 42, 42));

            return(gltfModel);
        }
        public void GenerateModel(string bboxWkt, DEMDataSet litto3DDataset, ImageryProvider imageryProvider, float zFactor = 3f, bool withTexture = true, bool withboat = true, bool withWaterSurface = true, DEMDataSet fallbackDataset = null)
        {
            try
            {
                bool   centerOnOrigin = true;
                int    TEXTURE_TILES  = 20;
                string outputDir      = Directory.GetCurrentDirectory();

                string modelName = $"{litto3DDataset.ResolutionMeters}m_z{zFactor}_{imageryProvider.Name}-{DateTime.Now:yyyyMMdd-hhmmss}";

                _logger.LogInformation($"Getting height map data...");


                var bbox      = GeometryService.GetBoundingBox(bboxWkt).ReprojectTo(4326, litto3DDataset.SRID);
                var heightMap = _elevationService.GetHeightMap(ref bbox, litto3DDataset);

                heightMap = heightMap.BakeCoordinates();
                var nullCoordsEnumerator = heightMap.Coordinates.Where(c => (c.Elevation ?? litto3DDataset.NoDataValue) == litto3DDataset.NoDataValue);

                var interpolator = _elevationService.GetInterpolator(InterpolationMode.Bilinear);
                using (IRasterFile raster = _rasterService.OpenFile(@"D:\Data\ELEVATION_DO_NOT_DELETE\GEBCO2020\cote azur\gebco_2020_subset.tif", DEMFileType.GEOTIFF))
                    using (RasterFileDictionary dic = new RasterFileDictionary())
                    {
                        var metadata = raster.ParseMetaData(fallbackDataset.FileFormat);
                        dic.Add(metadata, raster);
                        foreach (var pt in nullCoordsEnumerator)
                        {
                            var proj = pt.ReprojectTo(litto3DDataset.SRID, fallbackDataset.SRID);
                            pt.Elevation = _elevationService.GetElevationAtPoint(raster, dic, metadata, proj.Latitude, proj.Longitude, 0, interpolator, NoDataBehavior.UseNoDataDefinedInDem);
                        }
                    }

                var nullCoords              = heightMap.Coordinates.Where(c => (c.Elevation ?? litto3DDataset.NoDataValue) == litto3DDataset.NoDataValue).ToList();
                var nullCoordsFallbackProj  = nullCoords.ReprojectTo(litto3DDataset.SRID, fallbackDataset.SRID, nullCoords.Count);
                var nullCoordsFallbackProj2 = _elevationService.GetPointsElevation(nullCoordsFallbackProj, fallbackDataset, InterpolationMode.Bilinear, NoDataBehavior.UseNoDataDefinedInDem);

                ModelGenerationTransform transform         = new ModelGenerationTransform(bbox, 3857, centerOnOrigin: centerOnOrigin, zFactor, centerOnZOrigin: false);
                ModelGenerationTransform transformFrom4326 = new ModelGenerationTransform(bbox.ReprojectTo(2154, 4326), 3857, centerOnOrigin: centerOnOrigin, zFactor, centerOnZOrigin: false);

                _logger.LogInformation($"Processing height map data ({heightMap.Count} coordinates)...");
                heightMap = transform.TransformHeightMap(heightMap);

                var min = heightMap.Coordinates.Where(g => (g.Elevation ?? 0d) > litto3DDataset.NoDataValue).Min(g => g.Elevation ?? 0d);
                heightMap.Coordinates = heightMap.Coordinates.Select(p =>
                {
                    if (p.Elevation.GetValueOrDefault(0) <= litto3DDataset.NoDataValue)
                    {
                        p.Elevation = min;
                    }
                    return(p);
                });

                //=======================
                // Textures
                //
                PBRTexture pbrTexture = null;
                if (withTexture)
                {
                    var bbox4326 = bbox.ReprojectTo(2154, 4326);
                    Console.WriteLine("Download image tiles...");
                    TileRange tiles    = _imageryService.DownloadTiles(bbox4326, imageryProvider, TEXTURE_TILES);
                    string    fileName = Path.Combine(outputDir, "Texture.jpg");

                    Console.WriteLine("Construct texture...");
                    //TextureInfo texInfo = _imageryService.ConstructTexture(tiles, bbox4326, fileName, TextureImageFormat.image_jpeg);

                    TextureInfo texInfo;
                    if (withboat)
                    {
                        var trackPoints = GetGeoPointFromGeoJson(BoatCourseGeoJson);
                        texInfo = _imageryService.ConstructTextureWithGpxTrack(tiles, bbox4326, fileName, TextureImageFormat.image_jpeg, trackPoints, drawGpxVertices: true, color: SixLabors.ImageSharp.PixelFormats.Rgba32.Green, 30);
                    }
                    else
                    {
                        texInfo = _imageryService.ConstructTexture(tiles, bbox4326, fileName, TextureImageFormat.image_jpeg);
                    }


                    //
                    //=======================

                    //=======================
                    // Normal map
                    Console.WriteLine("Height map...");
                    //float Z_FACTOR = 0.00002f;

                    //hMap = hMap.CenterOnOrigin().ZScale(Z_FACTOR);
                    //var normalMap = _imageryService.GenerateNormalMap(heightMap, outputDir);

                    pbrTexture = PBRTexture.Create(texInfo, null);// normalMap);

                    //hMap = hMap.CenterOnOrigin(Z_FACTOR);
                    //
                    //=======================
                }
                // Triangulate height map
                // and add base and sides
                _logger.LogInformation($"Triangulating height map and generating 3D mesh...");

                heightMap = heightMap.BakeCoordinates();
                var coords = heightMap.Coordinates.ToList();

                var model = _sharpGltfService.CreateTerrainMesh(heightMap, pbrTexture);



                if (withWaterSurface)
                {
                    var bottomLeft  = coords[heightMap.Width * (heightMap.Height - 1)].AsVector3(); bottomLeft.Z = 0;
                    var topRight    = coords[heightMap.Width - 1].AsVector3(); topRight.Z = 0;
                    var topLeft     = coords[0].AsVector3(); topLeft.Z = 0;
                    var bottomRight = coords.Last().AsVector3(); bottomRight.Z = 0;

                    var waterSurface = _meshService.CreateWaterSurface(bottomLeft, topRight, topLeft, bottomRight,
                                                                       minZ: (float)min,
                                                                       color: VectorsExtensions.CreateColor(0, 150, 255, 64));
                    model = _sharpGltfService.AddMesh(model, "Water", waterSurface, doubleSided: true);
                }

                if (withboat)
                {
                    var boatInitPos = centerOnOrigin ? new GeoPoint(0, 0).AsVector3() : new GeoPoint(43.010625204304304, 6.3711613671060086).ReprojectTo(4326, 3857).AsVector3();
                    var axis        = _meshService.CreateAxis(2, 10, 3, 3).Translate(boatInitPos);
                    model = _sharpGltfService.AddMesh(model, "Boat", axis, doubleSided: false);
                    var boatCourse = transformFrom4326.TransformPoints(GetGeoPointFromGeoJson(BoatCourseGeoJson)).ToList(); //ReprojectGeodeticToCartesian().CenterOnOrigin().ToList();
                    model = _sharpGltfService.AddLine(model, "BoatCourse", boatCourse, VectorsExtensions.CreateColor(255, 0, 0, 128), 4);
                }



                model.SaveGLB(Path.Combine(Directory.GetCurrentDirectory(), modelName + ".glb"));



                _logger.LogInformation($"Model exported as {Path.Combine(Directory.GetCurrentDirectory(), modelName + ".gltf")} and .glb");

                //var point = new GeoPoint(43.01142119356318, 6.385200681010872).ReprojectTo(4326, 2154);
                //point = _elevationService.GetPointElevation(point, litto3DDataset);
            }
            catch (Exception e)
            {
                _logger.LogError(e, e.Message);
            }
        }
Esempio n. 5
0
        internal void Run(DEMDataSet dataSet, bool trackIn3D = true, bool generateTIN = false, int outputSrid = Reprojection.SRID_PROJECTED_LAMBERT_93)
        {
            try
            {
                string _gpxFile    = Path.Combine("SampleData", "GPX", "lake-pleasant-camping.gpx");
                bool   withTexture = true;
                float  Z_FACTOR    = 1.8f;
                float  Z_TRANSLATE_GPX_TRACK_METERS = 5;
                float  trailWidthMeters             = 5f;
                int    skipGpxPointsEvery           = 1;

                ImageryProvider provider = ImageryProvider.MapBoxSatellite; // new TileDebugProvider(null, maxDegreeOfParallelism: 1);//  ImageryProvider.MapBoxSatellite;

                string outputDir = Path.GetFullPath(".");

                //=======================
                /// Line strip from GPX
                ///
                // Get GPX points
                var segments = GpxImport.ReadGPX_Segments(_gpxFile);
                var points   = segments.SelectMany(seg => seg);
                var bbox     = points.GetBoundingBox().Scale(1.1, 1.1).ReprojectTo(4326, dataSet.SRID);
                // DEBUG
                // Test case : ASTER GDEMv3 : 5.5 43.5 Z=315
                // 303     307     308
                // 309    *315*    317
                // 314     321     324
                //points = GenerateDebugTrailPointsGenerateDebugTrailPoints(5.003, 5.006, 43.995, 43.997, 0.0001, 0.001);
                //points = GenerateDebugTrailPointsGenerateDebugTrailPoints(5.4990, 5.501, 43.4990, 43.501, 0.0001, 0.001);
                //points = GenerateDebugTrailPointsGenerateDebugTrailPoints(5.49, 5.51, 43.49, 43.51, 0.0005, 0.001);
                //bbox = points.GetBoundingBox().Scale(1.3,1.3);
                IEnumerable <GeoPoint> gpxPointsElevated = _elevationService.GetPointsElevation(points, dataSet);


                //
                //=======================

                //=======================
                /// Height map (get dem elevation for bbox)
                ///
                HeightMap hMap = _elevationService.GetHeightMap(ref bbox, dataSet);

                //                var refPoint = new GeoPoint(43.5, 5.5);
                //                hMap = hMap.BakeCoordinates();
                //                var hMapRefPoint = hMap.Coordinates.OrderBy(c => c.DistanceSquaredTo(refPoint)).First();
                //                var gpxRefPoint = gpxPointsElevated.OrderBy(c => c.DistanceSquaredTo(refPoint)).First();
                //                hMapRefPoint.Elevation += 60;
                //                gpxRefPoint.Elevation += 60;

                hMap = hMap.ReprojectTo(dataSet.SRID, outputSrid)
                       //.CenterOnOrigin()
                       .ZScale(Z_FACTOR)
                       .BakeCoordinates();
                //
                //=======================

                //=======================
                // Textures
                //
                PBRTexture pbrTexture = null;
                if (withTexture)
                {
                    Console.WriteLine("Download image tiles...");
                    TileRange tiles    = _imageryService.DownloadTiles(bbox, provider, 12);
                    string    fileName = Path.Combine(outputDir, "Texture.jpg");

                    Console.WriteLine("Construct texture...");
                    //TextureInfo texInfo = _imageryService.ConstructTextureWithGpxTrack(tiles, bbox, fileName, TextureImageFormat.image_jpeg, gpxPointsElevated, false);
                    TextureInfo texInfo = _imageryService.ConstructTexture(tiles, bbox, fileName, TextureImageFormat.image_jpeg);

                    //
                    //=======================

                    //=======================
                    // Normal map
                    Console.WriteLine("Height map...");
                    //float Z_FACTOR = 0.00002f;

                    //hMap = hMap.CenterOnOrigin().ZScale(Z_FACTOR);
                    //var normalMap = _imageryService.GenerateNormalMap(hMap, outputDir);
                    //pbrTexture = PBRTexture.Create(texInfo, normalMap);

                    pbrTexture = PBRTexture.Create(texInfo, null);

                    //hMap = hMap.CenterOnOrigin(Z_FACTOR);
                    //
                    //=======================
                }


                //=======================
                // MESH 3D terrain
                Console.WriteLine("Height map...");

                Console.WriteLine("GenerateTriangleMesh...");
                //hMap = _elevationService.GetHeightMap(bbox, _dataSet);
                ModelRoot model = null;
                if (generateTIN)
                {
                    model = TINGeneration.GenerateTIN(hMap, 10d, _sharpGltfService, pbrTexture, outputSrid);
                }
                else
                {
                    //hMap = hMap.CenterOnOrigin().ZScale(Z_FACTOR);
                    // generate mesh with texture
                    model = _sharpGltfService.CreateTerrainMesh(hMap, pbrTexture);
                }

                if (trackIn3D)
                {
                    // take 1 point evert nth
                    gpxPointsElevated = gpxPointsElevated.Where((x, i) => (i + 1) % skipGpxPointsEvery == 0);
                    gpxPointsElevated = gpxPointsElevated.ZTranslate(Z_TRANSLATE_GPX_TRACK_METERS)
                                        .ReprojectTo(dataSet.SRID, outputSrid)
                                        //.CenterOnOrigin()
                                        //.CenterOnOrigin(hMap.BoundingBox)
                                        .ZScale(Z_FACTOR);


                    model = _sharpGltfService.AddLine(model, "GPX", gpxPointsElevated, VectorsExtensions.CreateColor(255, 0, 0, 128), trailWidthMeters);
                }

                // model export
                Console.WriteLine("GenerateModel...");
                model.SaveGLB(Path.Combine(Directory.GetCurrentDirectory(), $"{GetType().Name} dst{dataSet.Name} TIN{generateTIN} Srid{outputSrid}.glb"));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
            }
        }
Esempio n. 6
0
        protected override ModelRoot AddToModel(ModelRoot gltfModel, string nodeName, IEnumerable <WaterModel> models)
        {
            gltfModel = _gltfService.AddLines(gltfModel, glTFNodeName, getLines(models), VectorsExtensions.CreateColor(102, 178, 255));

            return(gltfModel);
        }
Esempio n. 7
0
        /// <summary>
        /// Generates a VisualTopo file 3D model
        /// </summary>
        /// <remarks>LT* (Lambert Carto) projections are not supported and could produce imprecise results (shifted by +10meters)</remarks>
        /// <param name="vtopoFile">VisualTopo .TRO file</param>
        /// <param name="imageryProvider">Imagery provider for terrain texture. Set to null for untextured model</param>
        /// <param name="bboxMarginMeters">Terrain margin (meters) around VisualTopo model</param>
        public void Run_3DModelGeneration(string vtopoFile, ImageryProvider imageryProvider, float bboxMarginMeters = 1000, bool generateTopoOnlyModel = false, float zFactor = 1f)
        {
            try
            {
                //=======================
                // Generation params
                //
                int    outputSRID      = 3857;                         // Output SRID
                float  lineWidth       = 1.0F;                         // Topo lines width (meters)
                var    dataset         = DEMDataSet.AW3D30;            // DEM dataset for terrain and elevation
                int    TEXTURE_TILES   = 8;                            // Texture quality (number of tiles for bigger side) 4: med, 8: high, 12: ultra
                string outputDir       = Directory.GetCurrentDirectory();
                bool   GENERATE_LINE3D = false;

                //=======================
                // Open and parse file
                //
                // model will have available properties
                // => Graph (nodes/arcs)
                // => BoundingBox
                // => Topology3D -> list of point-to-point lines
                // => SRID of model file
                StopwatchLog    timeLog = StopwatchLog.StartNew(_logger);
                VisualTopoModel model   = _visualTopoService.LoadFile(vtopoFile, Encoding.GetEncoding("ISO-8859-1")
                                                                      , decimalDegrees: true
                                                                      , ignoreRadialBeams: true
                                                                      , zFactor);
                timeLog.LogTime($"Loading {vtopoFile} model file");

                // for debug,
                //var b = GetBranches(model); // graph list of all nodes
                //var lowestPoint = model.Sets.Min(s => s.Data.Min(d => d.GlobalGeoPoint?.Elevation ?? 0));

                BoundingBox bbox = model.BoundingBox                                                                                  // relative coords
                                   .Translate(model.EntryPoint.Longitude, model.EntryPoint.Latitude, model.EntryPoint.Elevation ?? 0) // absolute coords
                                   .Pad(bboxMarginMeters)                                                                             // margin around model
                                   .ReprojectTo(model.SRID, dataset.SRID);                                                            // DEM coords
                                                                                                                                      // Get height map
                                                                                                                                      // Note that ref Bbox means that the bbox will be adjusted to match DEM data
                var heightMap        = _elevationService.GetHeightMap(ref bbox, dataset, downloadMissingFiles: true);
                var bboxTerrainSpace = bbox.ReprojectTo(dataset.SRID, outputSRID);                                                    // terrain coords
                timeLog.LogTime("Terrain height map");

                //=======================
                // Get entry elevation (need to reproject to DEM coordinate system first)
                // and sections entry elevations
                //
                _visualTopoService.ComputeFullCavityElevations(model, dataset, zFactor); // will add TerrainElevationAbove and entry elevations
                _visualTopoService.Create3DTriangulation(model);
                timeLog.LogTime("Cavity points elevation");

                // Model origin
                GeoPoint axisOriginWorldSpace = model.EntryPoint.ReprojectTo(model.SRID, outputSRID)
                                                .CenterOnOrigin(bboxTerrainSpace);
                Vector3 axisOriginModelSpace = model.EntryPoint.AsVector3();

                //=======================
                // Local transform function from model coordinates (relative to entry, in meters)
                // and global coordinates absolute in final 3D model space
                //
                IEnumerable <GeoPoint> TransformLine(IEnumerable <GeoPoint> line)
                {
                    var newLine = line.Translate(model.EntryPoint)             // Translate to entry (=> global topo coord space)
                                  .ReprojectTo(model.SRID, outputSRID)         // Reproject to terrain coord space
                                  .CenterOnOrigin(bboxTerrainSpace)            // Center on terrain space origin
                                  .CenterOnOrigin(axisOriginWorldSpace);

                    return(newLine);
                };


                //=======================
                // 3D model
                //
                var gltfModel = _gltfService.CreateNewModel();

                // Add X/Y/Z axis on entry point
                var axis = _meshService.CreateAxis();
                _gltfService.AddMesh(gltfModel, "Axis", axis, doubleSided: false);

                int i = 0;

                var triangulation = model.TriangulationFull3D.Clone()
                                    .Translate(axisOriginModelSpace)             // already zScaled if zFactor > 1
                                    .ReprojectTo(model.SRID, outputSRID)
                                    .CenterOnOrigin(bboxTerrainSpace)
                                    .CenterOnOrigin(axisOriginWorldSpace.AsVector3());
                gltfModel = _gltfService.AddMesh(gltfModel, "Cavite3D", model.TriangulationFull3D, VectorsExtensions.CreateColor(0, 255, 0), doubleSided: false);

                if (GENERATE_LINE3D)
                {
                    foreach (var line in model.Topology3D) // model.Topology3D is the graph of topo paths
                    {
                        // Add line to model
                        gltfModel = _gltfService.AddLine(gltfModel
                                                         , string.Concat("GPX", i++)    // name of 3D node
                                                         , TransformLine(line)          // call transform function
                                                         , color: VectorsExtensions.CreateColor(255, 0, 0, 128)
                                                         , lineWidth);
                    }
                }
                timeLog.LogTime("Topo 3D model");


                //axis = _meshService.CreateAxis(10,100);
                //_gltfService.AddMesh(gltfModel, "Axis", axis, doubleSided: false);

                if (generateTopoOnlyModel)
                {
                    // Uncomment this to save 3D model for topo only (without terrain)
                    gltfModel.SaveGLB(string.Concat(Path.GetFileNameWithoutExtension(vtopoFile) + $"Z{zFactor}_TopoOnly.glb"));
                }

                // Reproject and center height map coordinates
                heightMap = heightMap.ReprojectTo(dataset.SRID, outputSRID)
                            .CenterOnOrigin(bboxTerrainSpace)
                            .ZScale(zFactor)
                            .CenterOnOrigin(axisOriginWorldSpace);
                //.BakeCoordinates();
                timeLog.LogTime("Height map transform");

                //=======================
                // Textures
                //
                PBRTexture pbrTexture = null;
                if (imageryProvider != null)
                {
                    TileRange tiles    = _imageryService.DownloadTiles(bbox, imageryProvider, TEXTURE_TILES);
                    string    fileName = Path.Combine(outputDir, "Texture.jpg");
                    timeLog.LogTime("Imagery download");

                    Console.WriteLine("Construct texture...");
                    //TextureInfo texInfo = _imageryService.ConstructTexture(tiles, bbox, fileName, TextureImageFormat.image_jpeg);
                    var         topoTexture = model.Topology3D.SelectMany(l => l).Translate(model.EntryPoint).ReprojectTo(model.SRID, 4326);
                    TextureInfo texInfo     = _imageryService.ConstructTextureWithGpxTrack(tiles, bbox, fileName, TextureImageFormat.image_jpeg
                                                                                           , topoTexture, false);

                    pbrTexture = PBRTexture.Create(texInfo, null);
                    timeLog.LogTime("Texture creation");
                }
                //
                //=======================

                // Triangulate height map
                _logger.LogInformation($"Triangulating height map and generating 3D mesh...");

                gltfModel = _gltfService.AddTerrainMesh(gltfModel, heightMap, pbrTexture);
                gltfModel.SaveGLB(string.Concat(Path.GetFileNameWithoutExtension(vtopoFile) + $"Z{zFactor}.glb"));
                timeLog.LogTime("3D model");
            }
            catch (Exception ex)
            {
                _logger.LogError("Error :" + ex.Message);
            }
        }