diff --git a/.gitmodules b/.gitmodules index 182027c..e054d29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,12 @@ [submodule "extern/Catch2"] path = extern/Catch2 url = https://github.com/catchorg/Catch2.git +[submodule "extern/libmorton"] + path = extern/libmorton + url = git@github.com:Forceflow/libmorton.git +[submodule "extern/rapidxml"] + path = extern/rapidxml + url = https://github.com/notviri/rapidxml.git +[submodule "extern/tiny-utf8"] + path = extern/tiny-utf8 + url = https://github.com/DuffsDevice/tiny-utf8.git diff --git a/CDBTo3DTiles/CMakeLists.txt b/CDBTo3DTiles/CMakeLists.txt index ebfc0ff..ec48c80 100644 --- a/CDBTo3DTiles/CMakeLists.txt +++ b/CDBTo3DTiles/CMakeLists.txt @@ -9,6 +9,9 @@ add_library(CDBTo3DTiles src/CDBGeometryVectors.cpp src/CDBElevation.cpp src/CDBImagery.cpp + src/CDBRMTexture.cpp + src/CDBRMDescriptor.cpp + src/CDBMaterials.cpp src/CDBModels.cpp src/CDBAttributes.cpp src/CDBDataset.cpp @@ -16,7 +19,8 @@ add_library(CDBTo3DTiles src/CDBTile.cpp src/CDBTileset.cpp src/CDB.cpp - src/CDBTo3DTiles.cpp) + src/CDBTo3DTiles.cpp + src/CDBTilesetBuilder.cpp) set(PRIVATE_INCLUDE_PATHS ${PROJECT_SOURCE_DIR}/src @@ -29,15 +33,25 @@ set(PRIVATE_THIRD_PARTY_INCLUDE_PATHS ${nlohmann_json_INCLUDE_DIRS} ${GDAL_INCLUDE_DIRS} ${tinygltf_INCLUDE_DIRS} + ${LIBMORTON_INCLUDE_DIR} + ${glm_INCLUDE_DIR} + ${rapidxml_INCLUDE_DIR} + ${tiny_utf8_INCLUDE_DIRS} ) -target_include_directories(CDBTo3DTiles +foreach(INCLUDE_DIR ${PRIVATE_THIRD_PARTY_INCLUDE_PATHS}) + target_include_directories(CDBTo3DTiles SYSTEM PUBLIC - "$>:${PRIVATE_THIRD_PARTY_INCLUDE_PATHS}>>" + ${INCLUDE_DIR} + ) +endforeach() +target_include_directories(CDBTo3DTiles PUBLIC - ${PROJECT_SOURCE_DIR}/include - "$>:${PRIVATE_INCLUDE_PATHS}>>") + ${CMAKE_CURRENT_LIST_DIR}/../Core/include + ${PROJECT_SOURCE_DIR}/include + ${PRIVATE_INCLUDE_PATHS} +) target_link_libraries(CDBTo3DTiles PRIVATE @@ -52,6 +66,7 @@ target_link_libraries(CDBTo3DTiles meshoptimizer Core ${GDAL_LIBRARIES}) +link_libraries(Core) set_property(TARGET CDBTo3DTiles PROPERTY diff --git a/CDBTo3DTiles/include/CDBTo3DTiles.h b/CDBTo3DTiles/include/CDBTo3DTiles.h index 7637e14..9a82b6c 100644 --- a/CDBTo3DTiles/include/CDBTo3DTiles.h +++ b/CDBTo3DTiles/include/CDBTo3DTiles.h @@ -4,6 +4,7 @@ #include #include #include +#include "../src/CDBTilesetBuilder.h" namespace CDBTo3DTiles { class GlobalInitializer @@ -23,8 +24,14 @@ class Converter void combineDataset(const std::vector &datasets); + void setUse3dTilesNext(bool use3dTilesNext); + + void setExternalSchema(bool externalSchema); + void setGenerateElevationNormal(bool generateElevationNormal); + void setSubtreeLevels(int subtreeLevels); + void setElevationLODOnly(bool elevationLOD); void setElevationDecimateError(float elevationDecimateError); @@ -34,9 +41,8 @@ class Converter void convert(); private: - struct Impl; struct TilesetCollection; - std::unique_ptr m_impl; + std::unique_ptr m_impl; }; } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/include/Utility.h b/CDBTo3DTiles/include/Utility.h index 6778ea3..51f7c5d 100644 --- a/CDBTo3DTiles/include/Utility.h +++ b/CDBTo3DTiles/include/Utility.h @@ -41,4 +41,32 @@ inline std::vector splitString(const std::string &str, const std::s results.emplace_back(str.substr(last)); return results; } + +inline uint64_t alignTo8(uint64_t v) +{ + return (v + 7) & ~7u; +} + +inline unsigned int countSetBitsInInteger(unsigned int integer) +{ + unsigned int count = 0; + while(integer > 0) + { + if ((integer & 1) == 1) + count += 1; + integer >>= 1; + } + return count; +} + +inline unsigned int countSetBitsInVectorOfInts(std::vector vec) +{ + unsigned int count = 0; + for(unsigned int integer : vec) + { + count += countSetBitsInInteger(integer); + } + return count; +} + } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDB.cpp b/CDBTo3DTiles/src/CDB.cpp index 822356e..dacc440 100644 --- a/CDBTo3DTiles/src/CDB.cpp +++ b/CDBTo3DTiles/src/CDB.cpp @@ -439,6 +439,34 @@ bool CDB::isImageryExist(const CDBTile &tile) const return std::filesystem::exists(imagery); } +bool CDB::isRMTextureExist(const CDBTile &tile) const +{ + CDBTile rmTextureTile = CDBTile(tile.getGeoCell(), + CDBDataset::RMTexture, + 1, + 1, + tile.getLevel(), + tile.getUREF(), + tile.getRREF()); + + auto rmTexture = m_path / (rmTextureTile.getRelativePath().string() + ".tif"); + return std::filesystem::exists(rmTexture); +} + +bool CDB::isRMDescriptorExist(const CDBTile &tile) const +{ + CDBTile rmDescriptorTile = CDBTile(tile.getGeoCell(), + CDBDataset::RMDescriptor, + 1, + 1, + tile.getLevel(), + tile.getUREF(), + tile.getRREF()); + + auto rmDescriptor = m_path / (rmDescriptorTile.getRelativePath().string() + ".xml"); + return std::filesystem::exists(rmDescriptor); +} + std::optional CDB::getImagery(const CDBTile &tile) const { CDBTile imageryTile = CDBTile(tile.getGeoCell(), @@ -464,6 +492,49 @@ std::optional CDB::getImagery(const CDBTile &tile) const return CDBImagery(std::move(imageryDataset), imageryTile); } +std::optional CDB::getRMTexture(const CDBTile &tile) const +{ + CDBTile rmTextureTile = CDBTile(tile.getGeoCell(), + CDBDataset::RMTexture, + 1, + 1, + tile.getLevel(), + tile.getUREF(), + tile.getRREF()); + + auto rmTexturePath = m_path / (rmTextureTile.getRelativePath().string() + ".tif"); + if (!std::filesystem::exists(rmTexturePath)) { + return std::nullopt; + } + + auto rmTextureDataset = GDALDatasetUniquePtr( + (GDALDataset *) GDALOpen(rmTexturePath.c_str(), GDALAccess::GA_ReadOnly)); + + if (!rmTextureDataset) { + return std::nullopt; + } + + return CDBRMTexture(std::move(rmTextureDataset), rmTextureTile); +} + +CDBRMDescriptor* CDB::getRMDescriptor(const CDBTile &tile) const +{ + CDBTile rmDescriptorTile = CDBTile(tile.getGeoCell(), + CDBDataset::RMDescriptor, + 1, + 1, + tile.getLevel(), + tile.getUREF(), + tile.getRREF()); + + auto rmDescriptorPath = m_path / (rmDescriptorTile.getRelativePath().string() + ".xml"); + if (!std::filesystem::exists(rmDescriptorPath)) { + return nullptr; + } + + return new CDBRMDescriptor(rmDescriptorPath, rmDescriptorTile); +} + void CDB::forEachDatasetTile(const CDBGeoCell &geoCell, CDBDataset dataset, std::function process) diff --git a/CDBTo3DTiles/src/CDB.h b/CDBTo3DTiles/src/CDB.h index 84ddc66..b341b26 100644 --- a/CDBTo3DTiles/src/CDB.h +++ b/CDBTo3DTiles/src/CDB.h @@ -3,6 +3,8 @@ #include "CDBElevation.h" #include "CDBGeometryVectors.h" #include "CDBImagery.h" +#include "CDBRMTexture.h" +#include "CDBRMDescriptor.h" #include "CDBModels.h" #include "CDBTileset.h" #include @@ -41,8 +43,16 @@ class CDB bool isImageryExist(const CDBTile &tile) const; + bool isRMTextureExist(const CDBTile &tile) const; + + bool isRMDescriptorExist(const CDBTile &tile) const; + std::optional getImagery(const CDBTile &tile) const; + std::optional getRMTexture(const CDBTile &tile) const; + + CDBRMDescriptor* getRMDescriptor(const CDBTile &tile) const; + static const std::filesystem::path TILES; static const std::filesystem::path METADATA; static const std::filesystem::path GTModel; diff --git a/CDBTo3DTiles/src/CDBAttributes.h b/CDBTo3DTiles/src/CDBAttributes.h index 3be8c3a..dc429ce 100644 --- a/CDBTo3DTiles/src/CDBAttributes.h +++ b/CDBTo3DTiles/src/CDBAttributes.h @@ -31,6 +31,61 @@ enum class CDBVectorCS2 PolygonFigurePointExtendedLevel = 20 }; +struct CDBAttributes +{ + std::map names; + std::map descriptions; + + CDBAttributes() + { + names["AHGT"] = "Absolute Height Flag"; + names["AO1"] = "Angle of Orientation"; + names["APID"] = "AirPort ID"; + names["BBH"] = "Bounding Box Height"; + names["BBL"] = "Bounding Box Length"; + names["BBW"] = "Bounding Box Width"; + names["BSR"] = "Bounding Sphere Radius"; + names["CMIX"] = "Composite Material Index"; + names["FSC"] = "Feature Classification Code"; + names["HGT"] = "Height above surface level"; + names["MLOD"] = "Model Level Of Detail"; + names["MODL"] = "Model Name"; + names["NIS"] = "Number of Instances"; + names["NIX"] = "Number of Indices"; + names["NNL"] = "Number of Normals"; + names["NTC"] = "Number of Texture Coordinates"; + names["NTX"] = "Number of Texels"; + names["NVT"] = "Number of Vertices"; + names["RTAI"] = "Relative Tactical Importance"; + names["RWID"] = "Runway ID"; + names["SSC"] = "Structure Shape Category"; + names["SSR"] = "Structure Shape of Roof"; + + descriptions["AHGT"] = "Indicates how to interpret the Z component of a vertex. If AHGT is true, the feature is positioned to the value specified by the Z component (Absolute Terrain Altitude), irrelevant of the terrain elevation dataset. If AHGT is false or not present, the feature is positioned to the value specified by the underlying terrain offset by the Z component value. Refer to section 5.6.1.1, ShapeFile Type Usage and Conventions for more details. AHGT can be present only in datasets using PointZ, PolylineZ, PolygonZ and MultiPointZ Shape types. AHGT should not be present for all other Shape types or must be ignored otherwise. Refer to Appendix A – \"How to Interpret the AHGT, HGT, BSR, BBH, and Z Attributes\" for additional usage guidelines. NOTE: It is recommended that the AHGT flag be set to false because it facilitates the creation of CDB datasets that are independent of each others. When the Z coordinate (altitude) of a feature is relative to the ground, the terrain elevation dataset can be updated without the need to recompute the altitude of the feature. CAUTION: When the AHGT flag is set to true, the feature will be at a fixed WGS-84 elevation independently of the terrain LOD selected by the client-device. As a result, there is no guarantee that the feature (and its modeled representation) will remain above the terrain skin across all terrain LODs. RECOMMENDATION: Limit the use of AHGT=TRUE to data whose source is inherently absolute. Such source data include geodetic marks or survey marks that provide a known position in terms of latitude, longitude, and altitude. Good examples of such markers are boundary markers between countries."; + descriptions["AO1"] = "The angular distance measured from true north (0 deg) clockwise to the major (Y) axis of the feature. If the feature is square, the axis 0 through 89.999 deg shall be recorded. If the feature is circular, 360.000 deg shall be recorded. Recommended Usage. CDB readers should default to a value of 0.000 if AO1 is missing. Applicable to Point, Light Point, Moving Model Location and Figure Point features. When used in conjunction with the PowerLine dataset, AO1 corresponds to the orientation of the Y-axis of the modeled pylon. The modeled pylon should be oriented (in its local Cartesian space) so that the wires nominally attach along the Y-axis."; + descriptions["APID"] = "A unique alphanumeric identifier that points to a record in the NavData Airport or Heliport dataset (i.e., a link to the Airport or the Heliport description in the NavData dataset). This ID is the value of the field Ident of the Airport or Heliport dataset. Note that all of the lights located in list-organized datasets that are associated with the operation of an airport (including runway lights and lighting systems) are required to reference an airport or heliport in the NavData dataset. All man-made features associated with an airport or heliport must be assigned an APID attribute; the APID attribute is not required for features unrelated to airports or heliports. Usage Note: Recommended for all Airport Light Points and airport-related i2DModels (such as runway/taxiway/apron surfaces, and markings). Failure to appropriately tag airport culture with APID attribute will result in reduced control of airport-related culture by simulator. Optional for Location Points, Environmental Light Points, and Moving Model Location features that fall within the confines of an airport and for which control of the feature is desirable."; + descriptions["BBH"] = "The Height/Width/Length of the Bounding Box of the 3D model associated with a point feature. It is the dimension of the box centered at the model origin and that bounds the portion of the model above its XY plane, including the envelopes of all articulated parts. BBH refers to height of the box above the XY plane of the model, BBW refers to the width of the box along the X-axis, and BBL refers to the length of the box along the Y-axis. Note that for 3D models used as cultural features, the XY plane of the model corresponds to its ground reference plane. The value of BBH, BBW and BBL should be accounted for by client-devices (in combination with other information) to determine the appropriate distance at which the model should be paged-in, rendered or processed. BBH, BBW and BBL are usually generated through database authoring tool automation. Optional on features for which a MODL has been assigned. When missing, CDB readers should default BBH to the value of BSR, and BBW and BBL to twice the value of BSR. The dimension of the bounding box is intrinsic to the model and identical for all LOD representations."; + descriptions["BBL"] = "The length of a feature."; + descriptions["BBW"] = "The width of a feature."; + descriptions["BSR"] = "The radius of a feature. In the case where a feature references an associated 3D model, it is the radius of the hemisphere centered at the model origin and that bounds the portion of the model above its XY plane, including the envelopes of all articulated parts. Note that for 3D models used as cultural features, the XY plane of the model corresponds to its ground reference plane. The value of BSR should be accounted for by client-devices (in combination with other information) to determine the appropriate distance at which the model should be paged-in, rendered or processed. When the feature does not reference a 3D model, BSR is the radius of the abstract point representing the feature (e.g., a city). "; + descriptions["CMIX"] = "Index into the Composite Material Table is used to determine the Base Materials composition of the associated feature."; + descriptions["FSC"] = "This code, in conjunction with the FACC is used to distinguish and categorize features within a dataset."; + descriptions["HGT"] = "Distance measured from the lowest point of the base at ground (non-floating objects) or water level (floating objects downhill side/downstream side) to the tallest point of the feature above the surface. Recorded values are positive numbers. In the case of roads and railroads, HGT corresponds to the elevation of the road/railroad wrt terrain in its immediate vicinity."; + descriptions["MLOD"] = "The level of detail of the 3D model associated with the point feature. When used in conjunction with MODL, the MLOD attribute indicates the LOD where the corresponding MODL is found. In this case, the value of MLOD can never be larger than the LOD of the Vector Tile-LOD that contains it. When used in the context of Airport and Environmental Light Point features, the value of MLOD, if present, indicates that this light point also exist in a 3D model found at the specified LOD. In such case, the value of MLOD is not constrained and can indicate any LOD."; + descriptions["MODL"] = " A string reference, the model name, which stands for the modeled geometry of a feature; in the case of buildings, this includes both its external shell and modeled interior. Usage Note: Needed for Point features, Road Figure Point features, Railroad Figure Point features, Pipeline Figure Point features and Hydrography Figure Point features that are modeled as OpenFlight or as RCS (Shape). MODL can also be used with Road Lineal features, Railroad Lineal features, Pipeline Lineal features and Hydrography Lineal and Areal features. Note that it is not permitted to specify a value for MODL simultaneously with a value for MMDC."; + descriptions["NIS"] = "Number of instances found in the 3D model associated with the cultural point feature."; + descriptions["NIX"] = "Number of indices found in the 3D model associated with the cultural point feature."; + descriptions["NNL"] = "Number of normal vectors found in the 3D model associated with the cultural point feature."; + descriptions["NTC"] = "Number of texture coordinates found in the 3D model associated with the cultural point feature."; + descriptions["NTX"] = "Number of texels found in the 3D model associated with the cultural point feature."; + descriptions["NVT"] = "Number of vertices of the 3D model associated with a point feature."; + descriptions["RTAI"] = "Provides the Relative TActical Importance of moving models or cultural features relative to other features for the purpose of client-device scene/load management. A value of 100% corresponds to the highest importance; a value of 0% corresponds to the lowest importance. When confronted with otherwise identical objects that differ only wrt to their RelativeTActical Importance, client-devices should always discard features with lower importance before those of higher importance in the course of performing their scene / load management function. As a result, a value of zero gives complete freedom to client-devices to discard the feature as soon as the load of the client-device is exceeded. The effectiveness of scene / load management functions can be severely hampered if large quantities of features are assigned the same Relative TActical Importance by the modeler. In effect, if all models are assigned the same value, the client-devices have no means to distinguish tactically important objects from each other. Assigning a value of 1% to all objects is equivalent to assigning them all a value of 99%. Ideally, the assignment of tactical importance to features should be in accordance to a histogram similar to the one shown here. The shape of the curve is not critical, however the proportion of models tagged with a high importance compared to those with low importance is critical in achieving effective scene/load management schemes. It is illustrated here to show that few models should have an importance of 100 with progressively more models with lower importance. The assignment of the RTAI to each feature lends itself to database tools automation. For instance, RTAI could be based on a look-up function which factors the feature’s type (FACC or MMDC). The value of Relative TActical Importance should be accounted for by client-devices (in combination with other information) to determine the appropriate distance at which the model should be rendered or processed. Relative TActical Importance is mandatory. It has no default value."; + descriptions["RWID"] = "An alphanumeric identifier that, combined with the APID, points to a unique record in the NavData Runway or Helipad dataset (i.e., a link to the Runway or Helipad description in the NavData dataset). This ID is the value of the field Ident of the Runway or Helipad dataset. Note that all of the lights and other features located in list-organized datasets that are associated with the operation of a runway or helipad are required to reference a runway or helipad in the NavData dataset; the RWID attribute is not required for features unrelated to a runway or helipad. Usage Note: Recommended for all Airport Light Points features. Failure to appropriately tag airport culture with RWID attribute will result in reduced control of runway-related (or helipad) culture by simulator. Optional for Point/Lineal/Areal features, Location Points Features, Environmental Light Point features, and Moving Model Location features that are associated with a runway and for which control of the feature is desirable."; + descriptions["SSC"] = "Describes the Geometric form, appearance, or configuration of the feature."; + descriptions["SSR"] = "Describes the roof shape."; + } +}; + class CDBInstancesAttributes { public: diff --git a/CDBTo3DTiles/src/CDBElevation.cpp b/CDBTo3DTiles/src/CDBElevation.cpp index 8bfdb21..228bc3f 100644 --- a/CDBTo3DTiles/src/CDBElevation.cpp +++ b/CDBTo3DTiles/src/CDBElevation.cpp @@ -12,12 +12,16 @@ static std::vector getRasterElevationHeights(GDALDatasetUniquePtr &raste static Mesh generateElevationMesh(const std::vector terrainHeights, Core::Cartographic topLeft, glm::uvec2 rasterSize, - glm::dvec2 pixelSize); + glm::dvec2 pixelSize, + double &minElevation, + double &maxElevation); static void loadElevation(const std::filesystem::path &path, Core::Cartographic topLeft, glm::ivec2 &rasterSize, - Mesh &mesh); + Mesh &mesh, + double &minElevation, + double &maxElevation); static void extractVerticesFromExistingSimplifiedMesh(const Mesh &existingMesh, Mesh &simplified, @@ -27,11 +31,13 @@ static void extractVerticesFromExistingSimplifiedMesh(const Mesh &existingMesh, unsigned idx1, unsigned idx2); -CDBElevation::CDBElevation(Mesh uniformGridMesh, size_t gridWidth, size_t gridHeight, CDBTile tile) +CDBElevation::CDBElevation(Mesh uniformGridMesh, size_t gridWidth, size_t gridHeight, CDBTile tile, double minElevation, double maxElevation) : m_gridWidth{gridWidth} , m_gridHeight{gridHeight} , m_uniformGridMesh{std::move(uniformGridMesh)} , m_tile{std::move(tile)} + , m_minElevation{minElevation} + , m_maxElevation{maxElevation} {} Mesh CDBElevation::createSimplifiedMesh(size_t targetIndexCount, float targetError) const @@ -201,7 +207,9 @@ std::optional CDBElevation::createFromFile(const std::filesystem:: Core::Cartographic topLeft(rectangle.getWest(), rectangle.getNorth()); glm::ivec2 rasterSize(0); Mesh uniformGridMesh; - loadElevation(file, topLeft, rasterSize, uniformGridMesh); + double min = 0; + double max = 0; + loadElevation(file, topLeft, rasterSize, uniformGridMesh, min, max); if (uniformGridMesh.positions.empty()) { return std::nullopt; @@ -211,7 +219,7 @@ std::optional CDBElevation::createFromFile(const std::filesystem:: size_t gridWidth = static_cast(rasterSize.x); size_t gridHeight = static_cast(rasterSize.y); - return CDBElevation(std::move(uniformGridMesh), gridWidth, gridHeight, *tile); + return CDBElevation(std::move(uniformGridMesh), gridWidth, gridHeight, *tile, min, max); } return std::nullopt; @@ -354,10 +362,41 @@ std::vector getRasterElevationHeights(GDALDatasetUniquePtr &rasterData, return elevationHeights; } +std::vector getRasterFeatureIDs(GDALDatasetUniquePtr &rasterData, glm::ivec2 rasterSize) +{ + auto rasterBand = rasterData->GetRasterBand(1); + auto rasterDataType = rasterBand->GetRasterDataType(); + if (rasterDataType != GDT_Byte) { + return {}; + } + + int rasterWidth = rasterSize.x; + int rasterHeight = rasterSize.y; + std::vector featureIDs(static_cast(rasterWidth * rasterHeight), 0); + if (GDALRasterIO(rasterBand, + GDALRWFlag::GF_Read, + 0, + 0, + rasterWidth, + rasterHeight, + featureIDs.data(), + rasterWidth, + rasterHeight, + GDALDataType::GDT_Byte, + 0, + 0 != CE_None)) { + return {}; + } + + return featureIDs; +} + Mesh generateElevationMesh(const std::vector elevationHeights, Core::Cartographic topLeft, glm::uvec2 rasterSize, - glm::dvec2 pixelSize) + glm::dvec2 pixelSize, + double &minElevation, + double &maxElevation) { // CDB uses only WG84 ellipsoid Core::Ellipsoid ellipsoid = Core::Ellipsoid::WGS84; @@ -381,12 +420,16 @@ Mesh generateElevationMesh(const std::vector elevationHeights, elevation.UVs.reserve(totalVertices); elevation.indices.reserve(totalIndices); + minElevation = std::numeric_limits::max(); + maxElevation = std::numeric_limits::lowest(); for (size_t y = 0; y < verticesHeight; ++y) { for (size_t x = 0; x < verticesWidth; ++x) { double longitude = topLeft.longitude + glm::radians(static_cast(x) * pixelSize.x); double latitude = topLeft.latitude + glm::radians(static_cast(y) * pixelSize.y); double height = static_cast( elevationHeights[glm::min(y, rasterHeight - 1) * rasterWidth + glm::min(x, rasterWidth - 1)]); + minElevation = glm::min(minElevation, height); + maxElevation = glm::max(maxElevation, height); Core::Cartographic cartographic(longitude, latitude, height); glm::dvec3 position = ellipsoid.cartographicToCartesian(cartographic); @@ -419,7 +462,9 @@ Mesh generateElevationMesh(const std::vector elevationHeights, void loadElevation(const std::filesystem::path &path, Core::Cartographic topLeft, glm::ivec2 &rasterSize, - Mesh &mesh) + Mesh &mesh, + double &minElevation, + double &maxElevation) { std::string file = path.string(); GDALDatasetUniquePtr rasterData = GDALDatasetUniquePtr( @@ -447,7 +492,7 @@ void loadElevation(const std::filesystem::path &path, } // generate elevation mesh - mesh = generateElevationMesh(elevationHeights, topLeft, rasterSize, pixelSize); + mesh = generateElevationMesh(elevationHeights, topLeft, rasterSize, pixelSize, minElevation, maxElevation); } } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBElevation.h b/CDBTo3DTiles/src/CDBElevation.h index 77c87c4..f10561b 100644 --- a/CDBTo3DTiles/src/CDBElevation.h +++ b/CDBTo3DTiles/src/CDBElevation.h @@ -11,7 +11,7 @@ namespace CDBTo3DTiles { class CDBElevation { public: - CDBElevation(Mesh uniformGridMesh, size_t gridWidth, size_t gridHeight, CDBTile tile); + CDBElevation(Mesh uniformGridMesh, size_t gridWidth, size_t gridHeight, CDBTile tile, double minElevation = 0, double maxElevation = 0); Mesh createSimplifiedMesh(size_t targetIndexCount, float targetError) const; @@ -21,6 +21,10 @@ class CDBElevation inline size_t getGridHeight() const noexcept { return m_gridHeight; } + inline double getMinElevation() { return m_minElevation; } + + inline double getMaxElevation() { return m_maxElevation; } + inline const CDBTile &getTile() const noexcept { return *m_tile; } inline void setTile(const CDBTile &tile) { m_tile = tile; } @@ -46,6 +50,8 @@ class CDBElevation size_t m_gridHeight; Mesh m_uniformGridMesh; std::optional m_tile; + double m_minElevation; + double m_maxElevation; }; } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBGeoCell.h b/CDBTo3DTiles/src/CDBGeoCell.h index 713f173..9bfb399 100644 --- a/CDBTo3DTiles/src/CDBGeoCell.h +++ b/CDBTo3DTiles/src/CDBGeoCell.h @@ -3,6 +3,7 @@ #include "Utility.h" #include #include +#include namespace CDBTo3DTiles { diff --git a/CDBTo3DTiles/src/CDBImagery.h b/CDBTo3DTiles/src/CDBImagery.h index 8e22d28..d391e48 100644 --- a/CDBTo3DTiles/src/CDBImagery.h +++ b/CDBTo3DTiles/src/CDBImagery.h @@ -9,8 +9,6 @@ class CDBImagery public: CDBImagery(GDALDatasetUniquePtr imageryDataset, const CDBTile &tile); - inline const GDALDataset &getData() const noexcept { return *_data; } - inline GDALDataset &getData() noexcept { return *_data; } inline const CDBTile &getTile() const noexcept { return *_tile; } diff --git a/CDBTo3DTiles/src/CDBMaterials.cpp b/CDBTo3DTiles/src/CDBMaterials.cpp new file mode 100644 index 0000000..ec719f1 --- /dev/null +++ b/CDBTo3DTiles/src/CDBMaterials.cpp @@ -0,0 +1,60 @@ +#include "CDBMaterials.h" +#include "rapidxml_utils.hpp" + +#include + +namespace CDBTo3DTiles { + +static std::string MATERIAL_CLASS_NAME = "CDBMaterialsClass"; +static std::string BASE_MATERIAL_ENUM_NAME = "CDBBaseMaterial"; + +void CDBMaterials::readBaseMaterialsFile(std::filesystem::path materialsXmlPath) +{ + rapidxml::file<> xmlFile(materialsXmlPath.c_str()); + rapidxml::xml_document<> xml; + xml.parse<0>(xmlFile.data()); + + rapidxml::xml_node<> *baseMaterialTableNode = xml.first_node("Base_Material_Table"); + int i = 0; + for (rapidxml::xml_node<> *baseMaterialNode = baseMaterialTableNode->first_node("Base_Material"); + baseMaterialNode; + baseMaterialNode = baseMaterialNode->next_sibling()) { + std::string name = baseMaterialNode->first_node("Name")->value(); + std::string description = baseMaterialNode->first_node("Description")->value(); + + CDBBaseMaterial baseMaterial(name, description, i++); + baseMaterials.insert(std::make_pair(name, baseMaterial)); + } +} + +nlohmann::json CDBMaterials::generateSchema() +{ + nlohmann::json materialClass = nlohmann::json::object(); + // Composite material names. + materialClass["properties"]["name"] = {{"type", "STRING"}}; + // Composite material substrate compositions. + materialClass["properties"]["substrates"] = {{"type", "ARRAY"}, + {"componentType", "ENUM"}, + {"enumType", BASE_MATERIAL_ENUM_NAME}}; + // Composite material substrate weights. + materialClass["properties"]["weights"] = {{"type", "ARRAY"}, {"componentType", "UINT8"}}; + + // Enum of base materials + nlohmann::json baseMaterialEnum = nlohmann::json::object(); + baseMaterialEnum["valueType"] = "UINT8"; + nlohmann::json values = nlohmann::json::array(); + for (auto const &[key, material] : baseMaterials) { + values.emplace_back(nlohmann::json{{"name", material.name}, + {"description", material.description}, + {"value", material.value}}); + } + baseMaterialEnum["values"] = values; + + nlohmann::json schema = nlohmann::json::object(); + schema["classes"][MATERIAL_CLASS_NAME] = materialClass; + schema["enums"][BASE_MATERIAL_ENUM_NAME] = baseMaterialEnum; + + return schema; +} + +} // namespace CDBTo3DTiles \ No newline at end of file diff --git a/CDBTo3DTiles/src/CDBMaterials.h b/CDBTo3DTiles/src/CDBMaterials.h new file mode 100644 index 0000000..be05cee --- /dev/null +++ b/CDBTo3DTiles/src/CDBMaterials.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace CDBTo3DTiles { + +struct CDBBaseMaterial +{ + std::string name; + std::string description; + int value; + // ENHANCEMENT: Add color property to use in styling. + + CDBBaseMaterial(std::string _name, std::string _description, int _value) + : name(_name) + , description(_description) + , value(_value) + {} +}; + +struct CDBCompositeMaterial +{ + int index; + std::string name; + std::map weightedMaterials; +}; + +class CDBMaterials +{ +public: + void readBaseMaterialsFile(std::filesystem::path materialsXmlPath); + nlohmann::json generateSchema(); + std::map baseMaterials; +}; +} // namespace CDBTo3DTiles \ No newline at end of file diff --git a/CDBTo3DTiles/src/CDBRMDescriptor.cpp b/CDBTo3DTiles/src/CDBRMDescriptor.cpp new file mode 100644 index 0000000..2c99af7 --- /dev/null +++ b/CDBTo3DTiles/src/CDBRMDescriptor.cpp @@ -0,0 +1,110 @@ +#include "CDBRMDescriptor.h" +#include "rapidxml_utils.hpp" +#include "tiny_gltf.h" +#include +#include +#include +#include "Gltf.h" +#include + +namespace CDBTo3DTiles { + +static const std::string CDB_MATERIAL_CLASS_NAME = "CDBMaterialsClass"; +static const std::string CDB_MATERIAL_FEATURE_TABLE_NAME = "CDBMaterialFeatureTable"; +static const std::string SCHEMA_PATH = "../../../../../materials.json"; + +CDBRMDescriptor::CDBRMDescriptor(std::filesystem::path xmlPath, const CDBTile &tile) + : _xmlPath{xmlPath} + , _tile{tile} +{} + +void CDBRMDescriptor::addFeatureTableToGltf(CDBMaterials *materials, tinygltf::Model *gltf, bool externalSchema) +{ + // Parse RMDescriptor XML file. + rapidxml::file<> xmlFile(_xmlPath.c_str()); + rapidxml::xml_document<> xml; + xml.parse<0>(xmlFile.data()); + + // Build vector of Composite Material names. + std::vector> compositeMaterialNames = {{0}}; + std::vector substrates = {0}; + std::vector weights = {0}; + std::vector arrayOffsets = {0, 1}; // Substrates and weights can share an arrayOffsetBuffer + size_t substrateOffset = 1; + std::vector compositeMaterialNameOffsets = {0,1}; + size_t currentOffset = 1; + + // Iterate through Composite_Material(s). + rapidxml::xml_node<> *tableNode = xml.first_node("Composite_Material_Table"); + for (rapidxml::xml_node<> *materialNode = tableNode->first_node("Composite_Material"); materialNode; + materialNode = materialNode->next_sibling()) { + rapidxml::xml_node<> *materialNameNode = materialNode->first_node("Name"); + + // Iterate through Material(s). + rapidxml::xml_node<> *substrateNode = materialNode->first_node("Primary_Substrate"); + for (rapidxml::xml_node<> *baseMaterialNode = substrateNode->first_node("Material"); baseMaterialNode; + baseMaterialNode = baseMaterialNode->next_sibling()) { + // Increase substrate offset. + substrateOffset++; + // Get base material index from name. + auto baseMaterial = &materials->baseMaterials.find(baseMaterialNode->first_node("Name")->value())->second; + int enumIndex = baseMaterial->value; + // Get weight. + int weight = std::stoi(baseMaterialNode->first_node("Weight")->value()); + + substrates.emplace_back(enumIndex); + weights.emplace_back(weight); + } + // Insert substrate offset. + arrayOffsets.emplace_back(substrateOffset); + + // Insert name. + std::string name = materialNameNode->value(); + compositeMaterialNames.emplace_back(std::vector (name.begin(), name.end())); + + // Update start offset of next name. + currentOffset += materialNameNode->value_size(); + // Insert offset. + compositeMaterialNameOffsets.emplace_back(currentOffset); + } + + unsigned int stringOffsetBufferViewIndex = createMetadataBufferView(gltf, compositeMaterialNameOffsets); + unsigned int stringsBufferViewIndex = createMetadataBufferView(gltf, compositeMaterialNames, currentOffset); + unsigned int substratesBufferViewIndex = createMetadataBufferView(gltf, substrates); + unsigned int weightsBufferViewIndex = createMetadataBufferView(gltf, weights); + unsigned int arrayOffsetBufferViewIndex = createMetadataBufferView(gltf, arrayOffsets); + + // Setup feature table. + nlohmann::json featureTable = nlohmann::json::object(); + featureTable["class"] = CDB_MATERIAL_CLASS_NAME; + featureTable["count"] = compositeMaterialNames.size(); + + featureTable["properties"]["name"]["bufferView"] = stringsBufferViewIndex; + featureTable["properties"]["name"]["offsetType"] = "UINT8"; + featureTable["properties"]["name"]["stringOffsetBufferView"] = stringOffsetBufferViewIndex; + + + featureTable["properties"]["substrates"]["bufferView"] = substratesBufferViewIndex; + featureTable["properties"]["substrates"]["offsetType"] = "UINT8"; + featureTable["properties"]["substrates"]["arrayOffsetBufferView"] = arrayOffsetBufferViewIndex; + + featureTable["properties"]["weights"]["bufferView"] = weightsBufferViewIndex; + featureTable["properties"]["weights"]["offsetType"] = "UINT8"; + featureTable["properties"]["weights"]["arrayOffsetBufferView"] = arrayOffsetBufferViewIndex; + + // Create EXT_feature_metadata extension and add it to glTF. + nlohmann::json extension = nlohmann::json::object(); + if (externalSchema) + extension["schemaUri"] = SCHEMA_PATH; + else + extension["schema"] = materials->generateSchema(); + extension["featureTables"][CDB_MATERIAL_FEATURE_TABLE_NAME] = featureTable; + + tinygltf::Value extensionValue; + CDBTo3DTiles::ParseJsonAsValue(&extensionValue, extension); + gltf->extensions.insert( + std::pair(std::string("EXT_feature_metadata"), extensionValue)); + gltf->extensionsUsed.emplace_back("EXT_feature_metadata"); +} + +} // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBRMDescriptor.h b/CDBTo3DTiles/src/CDBRMDescriptor.h new file mode 100644 index 0000000..aa6064e --- /dev/null +++ b/CDBTo3DTiles/src/CDBRMDescriptor.h @@ -0,0 +1,20 @@ + +#pragma once + +#include "CDBTileset.h" +#include "gdal_priv.h" +#include "rapidxml.hpp" +#include "CDBMaterials.h" +#include "Gltf.h" + +namespace CDBTo3DTiles { +class CDBRMDescriptor +{ +private: + std::filesystem::path _xmlPath; + std::optional _tile; +public: + CDBRMDescriptor(std::filesystem::path xmlPath, const CDBTile &tile); + void addFeatureTableToGltf(CDBMaterials *materials, tinygltf::Model *gltf, bool useExternalSchema); +}; +} // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBRMTexture.cpp b/CDBTo3DTiles/src/CDBRMTexture.cpp new file mode 100644 index 0000000..d6840ab --- /dev/null +++ b/CDBTo3DTiles/src/CDBRMTexture.cpp @@ -0,0 +1,10 @@ +#include "CDBRMTexture.h" + +namespace CDBTo3DTiles { + +CDBRMTexture::CDBRMTexture(GDALDatasetUniquePtr rmTextureDataset, const CDBTile &tile) + : _data{std::move(rmTextureDataset)} + , _tile{tile} +{} + +} // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBRMTexture.h b/CDBTo3DTiles/src/CDBRMTexture.h new file mode 100644 index 0000000..984d4a2 --- /dev/null +++ b/CDBTo3DTiles/src/CDBRMTexture.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CDBTileset.h" +#include "gdal_priv.h" + +namespace CDBTo3DTiles { +class CDBRMTexture +{ +public: + CDBRMTexture(GDALDatasetUniquePtr rmTextureDataset, const CDBTile &tile); + + inline GDALDataset &getData() noexcept { return *_data; } + + inline const CDBTile &getTile() const noexcept { return *_tile; } + +private: + GDALDatasetUniquePtr _data; + std::optional _tile; +}; +} // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/CDBTile.cpp b/CDBTo3DTiles/src/CDBTile.cpp index d1a3aa0..e96c27f 100644 --- a/CDBTo3DTiles/src/CDBTile.cpp +++ b/CDBTo3DTiles/src/CDBTile.cpp @@ -40,12 +40,14 @@ CDBTile::CDBTile(CDBGeoCell geoCell, CDBDataset dataset, int CS_1, int CS_2, int m_RREF = RREF; m_region = calcBoundRegion(*m_geoCell, m_level, m_UREF, m_RREF); m_path = convertToPath(); + m_pathWithNonZeroPaddedLevel = convertToPathWithNonZeroPaddedLevel(); } CDBTile::CDBTile(const CDBTile &other) : m_customContentURI{other.m_customContentURI} , m_region{other.m_region} , m_path{other.m_path} + , m_pathWithNonZeroPaddedLevel{other.m_pathWithNonZeroPaddedLevel} , m_geoCell{other.m_geoCell} , m_dataset{other.m_dataset} , m_CS_1{other.m_CS_1} @@ -61,6 +63,7 @@ CDBTile &CDBTile::operator=(const CDBTile &other) m_customContentURI = other.m_customContentURI; m_region = other.m_region; m_path = other.m_path; + m_pathWithNonZeroPaddedLevel = other.m_pathWithNonZeroPaddedLevel; m_geoCell = other.m_geoCell; m_dataset = other.m_dataset; m_CS_1 = other.m_CS_1; @@ -325,6 +328,15 @@ std::string CDBTile::getLevelInFilename() const noexcept return "L" + toStringWithZeroPadding(2, m_level); } +std::string CDBTile::getLevelWithNoZeroPaddingInFilename() const noexcept +{ + if (m_level < 0) { + return "LC" + std::to_string(glm::abs(m_level)); + } + + return "L" + std::to_string(m_level); +} + std::string CDBTile::getLevelDirectoryName() const noexcept { if (m_level < 0) { @@ -357,6 +369,29 @@ std::filesystem::path CDBTile::convertToPath() const noexcept return CDB::TILES / latitudeDir / longitudeDir / datasetDir / levelDir / UREFDir / tileFilename; } +std::filesystem::path CDBTile::convertToPathWithNonZeroPaddedLevel() const noexcept +{ + std::string latitudeDir = m_geoCell->getLatitudeDirectoryName(); + std::string longitudeDir = m_geoCell->getLongitudeDirectoryName(); + + std::string datasetDir = getCDBDatasetDirectoryName(m_dataset); + std::string datasetInTileFilename = "D" + toStringWithZeroPadding(3, static_cast(m_dataset)); + + std::string levelDir = getLevelDirectoryName(); + std::string levelInTileFilename = getLevelWithNoZeroPaddingInFilename(); + + std::string UREFDir = getUREFDirectoryName(); + std::string RREFName = getRREFName(); + + std::string CS_1 = getCS_1Name(); + std::string CS_2 = getCS_2Name(); + + std::string tileFilename = latitudeDir + longitudeDir + "_" + datasetInTileFilename + "_" + CS_1 + "_" + + CS_2 + "_" + levelInTileFilename + "_" + UREFDir + "_" + RREFName; + + return CDB::TILES / latitudeDir / longitudeDir / datasetDir / levelDir / UREFDir / tileFilename; +} + Core::BoundingRegion CDBTile::calcBoundRegion(const CDBGeoCell &geoCell, int level, int UREF, int RREF) noexcept { double distLOD = 1.0; diff --git a/CDBTo3DTiles/src/CDBTile.h b/CDBTo3DTiles/src/CDBTile.h index a8a47b9..4cc8803 100644 --- a/CDBTo3DTiles/src/CDBTile.h +++ b/CDBTo3DTiles/src/CDBTile.h @@ -20,8 +20,12 @@ class CDBTile inline const std::filesystem::path &getRelativePath() const noexcept { return m_path; } + inline const std::filesystem::path &getRelativePathWithNonZeroPaddedLevel() const noexcept { return m_pathWithNonZeroPaddedLevel; } + inline const Core::BoundingRegion &getBoundRegion() const noexcept { return *m_region; } + inline void setBoundRegion(const Core::BoundingRegion& boundRegion) const noexcept { m_region = boundRegion; } + inline const CDBGeoCell &getGeoCell() const noexcept { return *m_geoCell; } inline CDBDataset getDataset() const noexcept { return m_dataset; } @@ -74,6 +78,8 @@ class CDBTile std::string getLevelInFilename() const noexcept; + std::string getLevelWithNoZeroPaddingInFilename() const noexcept; + std::string getRREFName() const noexcept; std::string getCS_1Name() const noexcept; @@ -84,10 +90,13 @@ class CDBTile std::filesystem::path convertToPath() const noexcept; + std::filesystem::path convertToPathWithNonZeroPaddedLevel() const noexcept; + std::vector m_children; std::optional m_customContentURI; - std::optional m_region; + mutable std::optional m_region; std::filesystem::path m_path; + std::filesystem::path m_pathWithNonZeroPaddedLevel; // for implicit tiling std::optional m_geoCell; CDBDataset m_dataset; int m_CS_1; diff --git a/CDBTo3DTiles/src/CDBTileset.cpp b/CDBTo3DTiles/src/CDBTileset.cpp index 905439c..71f774d 100644 --- a/CDBTo3DTiles/src/CDBTileset.cpp +++ b/CDBTo3DTiles/src/CDBTileset.cpp @@ -27,6 +27,35 @@ const CDBTile *CDBTileset::getRoot() const return m_tiles.front().get(); } +const CDBTile* CDBTileset::getFirstTileAtLevel(int level) const +{ + const CDBTile *root = getRoot(); + if(!root) + return nullptr; + if (root->getLevel() == level) + return root; + return getFirstTileAtLevel(root, level); +} + +const CDBTile* CDBTileset::getFirstTileAtLevel(const CDBTile *root, int level) const +{ + std::vector children = root->getChildren(); + if (children.empty()) + return nullptr; + for(auto childCandidate : children) + { + if(childCandidate) + { + if (childCandidate->getLevel() == level) + return childCandidate; + const CDBTile *tileAtLevelCandidate = getFirstTileAtLevel(childCandidate, level); + if(tileAtLevelCandidate) + return tileAtLevelCandidate; + } + } + return nullptr; +} + CDBTile *CDBTileset::insertTile(const CDBTile &tile) { if (tile.getLevel() < m_rootLevel) { @@ -94,12 +123,12 @@ const CDBTile *CDBTileset::getFitTile(const CDBTile *root, Core::Cartographic ca } } } - return root; } CDBTile *CDBTileset::insertTileRecursively(const CDBTile &insert, CDBTile *subTree) { + subTree->setBoundRegion(subTree->getBoundRegion().computeUnion(insert.getBoundRegion())); // we are at the right level here. Just copy the data over if (insert.getLevel() == subTree->getLevel() && insert.getUREF() == subTree->getUREF() && insert.getRREF() == subTree->getRREF()) { @@ -107,7 +136,6 @@ CDBTile *CDBTileset::insertTileRecursively(const CDBTile &insert, CDBTile *subTr if (customContentURI) { subTree->setCustomContentURI(*customContentURI); } - return subTree; } @@ -154,7 +182,6 @@ CDBTile *CDBTileset::insertTileRecursively(const CDBTile &insert, CDBTile *subTr childLevel, childUREF, childRREF); - children[childIdx] = child.get(); m_tiles.emplace_back(std::move(child)); } diff --git a/CDBTo3DTiles/src/CDBTileset.h b/CDBTo3DTiles/src/CDBTileset.h index 20c1b31..e8d586f 100644 --- a/CDBTo3DTiles/src/CDBTileset.h +++ b/CDBTo3DTiles/src/CDBTileset.h @@ -17,6 +17,8 @@ class CDBTileset const CDBTile *getRoot() const; + const CDBTile *getFirstTileAtLevel(int level) const; + CDBTile *insertTile(const CDBTile &tile); const CDBTile *getFitTile(Core::Cartographic cartographic) const; @@ -24,6 +26,8 @@ class CDBTileset private: const CDBTile *getFitTile(const CDBTile *root, Core::Cartographic cartographic) const; + const CDBTile *getFirstTileAtLevel(const CDBTile *root, int level) const; + CDBTile *insertTileRecursively(const CDBTile &insert, CDBTile *subTree); int m_rootLevel; diff --git a/CDBTo3DTiles/src/CDBTilesetBuilder.cpp b/CDBTo3DTiles/src/CDBTilesetBuilder.cpp new file mode 100644 index 0000000..51a2f7f --- /dev/null +++ b/CDBTo3DTiles/src/CDBTilesetBuilder.cpp @@ -0,0 +1,1087 @@ +#include "CDBTilesetBuilder.h" +#include "CDB.h" +#include "CDBRMDescriptor.h" +#include "FileUtil.h" +#include "Gltf.h" +#include "MathHelpers.h" +#include "TileFormatIO.h" +#include "gdal.h" +#include "osgDB/WriteFile" +#include +#include +#include +#include +using json = nlohmann::json; +using namespace CDBTo3DTiles; + +const std::string CDBTilesetBuilder::ELEVATIONS_PATH = "Elevation"; +const std::string CDBTilesetBuilder::ROAD_NETWORK_PATH = "RoadNetwork"; +const std::string CDBTilesetBuilder::RAILROAD_NETWORK_PATH = "RailRoadNetwork"; +const std::string CDBTilesetBuilder::POWERLINE_NETWORK_PATH = "PowerlineNetwork"; +const std::string CDBTilesetBuilder::HYDROGRAPHY_NETWORK_PATH = "HydrographyNetwork"; +const std::string CDBTilesetBuilder::GTMODEL_PATH = "GTModels"; +const std::string CDBTilesetBuilder::GSMODEL_PATH = "GSModels"; +const int CDBTilesetBuilder::MAX_LEVEL = 23; + +const std::unordered_set CDBTilesetBuilder::DATASET_PATHS = {ELEVATIONS_PATH, + ROAD_NETWORK_PATH, + RAILROAD_NETWORK_PATH, + POWERLINE_NETWORK_PATH, + HYDROGRAPHY_NETWORK_PATH, + GTMODEL_PATH, + GSMODEL_PATH}; +void CDBTilesetBuilder::flushTilesetCollection( + const CDBGeoCell &geoCell, + std::unordered_map &tilesetCollections, + bool replace) +{ + auto geoCellCollectionIt = tilesetCollections.find(geoCell); + if (geoCellCollectionIt != tilesetCollections.end()) { + const auto &tilesetCollection = geoCellCollectionIt->second; + const auto &CSToPaths = tilesetCollection.CSToPaths; + for (const auto &CSTotileset : tilesetCollection.CSToTilesets) { + const auto &tileset = CSTotileset.second; + auto root = tileset.getRoot(); + if (!root) { + continue; + } + int maxLevel = 0; + for (int level = 0; level < MAX_LEVEL + 1; level += 1) { + if (tileset.getFirstTileAtLevel(level)) + maxLevel = level; + } + + auto tilesetDirectory = CSToPaths.at(CSTotileset.first); + auto tilesetJsonPath = tilesetDirectory + / (CDBTile::retrieveGeoCellDatasetFromTileName(*root) + ".json"); + + // write to tileset.json file + std::ofstream fs(tilesetJsonPath); + + writeToTilesetJson(tileset, replace, fs, use3dTilesNext, subtreeLevels, maxLevel, {}); + + // add tileset json path to be combined later for multiple geocell + // remove the output root path to become relative path + tilesetJsonPath = std::filesystem::relative(tilesetJsonPath, outputPath); + defaultDatasetToCombine.emplace_back(tilesetJsonPath); + } + + tilesetCollections.erase(geoCell); + } +} + +void CDBTilesetBuilder::flushAvailabilitiesAndWriteSubtrees() +{ + std::set subtreeRoots; + + // write all of the availability buffers and subtree files for each dataset group + for (auto &[dataset, csTileAndChildAvailabilities] : datasetCSTileAndChildAvailabilities) { + if (datasetCSSubtrees.count(dataset) == 0) { + continue; + } + for (auto &[CSKey, subtreeMap] : datasetCSSubtrees.at(dataset)) { + std::map &tileAndChildAvailabilities + = csTileAndChildAvailabilities.at(CSKey); + for (auto &[key, subtree] : subtreeMap) { + subtreeRoots.insert(key); + + bool constantNodeAvailability = (subtree.nodeCount == 0) + || (subtree.nodeCount == subtreeNodeCount); + + if (constantNodeAvailability) { + continue; + } + + std::vector outputBuffer(nodeAvailabilityByteLengthWithPadding); + uint8_t *outBuffer = &outputBuffer[0]; + memset(&outBuffer[0], 0, nodeAvailabilityByteLengthWithPadding); + memcpy(&outBuffer[0], &subtree.nodeBuffer[0], nodeAvailabilityByteLengthWithPadding); + std::filesystem::path path = datasetDirs.at(dataset) / CSKey / "availability" + / (key + ".bin"); + Utilities::writeBinaryFile(path, + (const char *) &outBuffer[0], + nodeAvailabilityByteLengthWithPadding); + } + + // write .subtree files for every subtree + for (std::string subtreeRoot : subtreeRoots) { + json subtreeJson; + + nlohmann::json buffers = nlohmann::json::array(); + int bufferIndex = 0; + nlohmann::json bufferViews = nlohmann::json::array(); + SubtreeAvailability tileAndChildAvailability = tileAndChildAvailabilities.at(subtreeRoot); + tileAndChildAvailability.nodeCount = countSetBitsInVectorOfInts( + tileAndChildAvailability.nodeBuffer); + tileAndChildAvailability.childCount = countSetBitsInVectorOfInts( + tileAndChildAvailability.childBuffer); + bool constantTileAvailability = (tileAndChildAvailability.nodeCount == 0) + || (tileAndChildAvailability.nodeCount == subtreeNodeCount); + bool constantChildAvailability = (tileAndChildAvailability.childCount == 0) + || (tileAndChildAvailability.childCount == childSubtreeCount); + + uint64_t nodeBufferLengthToWrite = static_cast(!constantTileAvailability) + * nodeAvailabilityByteLengthWithPadding; + uint64_t childBufferLengthToWrite = static_cast(!constantChildAvailability) + * childSubtreeAvailabilityByteLengthWithPadding; + long unsigned int bufferByteLength = nodeBufferLengthToWrite + childBufferLengthToWrite; + if (bufferByteLength != 0) { + nlohmann::json byteLength; + byteLength["byteLength"] = bufferByteLength; + buffers.emplace_back(byteLength); + bufferIndex += 1; + } + + std::vector internalBuffer(bufferByteLength); + memset(&internalBuffer[0], 0, bufferByteLength); + uint8_t *outInternalBuffer = &internalBuffer[0]; + nlohmann::json tileAvailabilityJson; + int bufferViewIndex = 0; + uint64_t internalBufferOffset = 0; + if (constantTileAvailability) + tileAvailabilityJson["constant"] = static_cast(tileAndChildAvailability.nodeCount + == subtreeNodeCount); + else { + memcpy(&outInternalBuffer[0], + &tileAndChildAvailability.nodeBuffer[0], + nodeAvailabilityByteLengthWithPadding); + nlohmann::json bufferViewObj; + bufferViewObj["buffer"] = 0; + bufferViewObj["byteOffset"] = 0; + bufferViewObj["byteLength"] = availabilityByteLength; + bufferViews.emplace_back(bufferViewObj); + internalBufferOffset += nodeAvailabilityByteLengthWithPadding; + tileAvailabilityJson["bufferView"] = bufferViewIndex; + bufferViewIndex += 1; + } + subtreeJson["tileAvailability"] = tileAvailabilityJson; + + nlohmann::json childAvailabilityJson; + if (constantChildAvailability) + childAvailabilityJson["constant"] = static_cast(tileAndChildAvailability.childCount + == childSubtreeCount); + else { + memcpy(&outInternalBuffer[internalBufferOffset], + &tileAndChildAvailability.childBuffer[0], + childSubtreeAvailabilityByteLengthWithPadding); + nlohmann::json bufferViewObj; + bufferViewObj["buffer"] = 0; + bufferViewObj["byteOffset"] = internalBufferOffset; + bufferViewObj["byteLength"] = childSubtreeAvailabilityByteLength; + bufferViews.emplace_back(bufferViewObj); + childAvailabilityJson["bufferView"] = bufferViewIndex; + bufferViewIndex += 1; + } + subtreeJson["childSubtreeAvailability"] = childAvailabilityJson; + + std::string availabilityFileName = subtreeRoot + ".bin"; + + std::filesystem::path datasetDir = datasetDirs.at(dataset); + + std::map csSubtreeRoots = datasetCSSubtrees.at(dataset).at( + CSKey); + nlohmann::json contentObj; + if (std::filesystem::exists(datasetDir / CSKey / "availability" / availabilityFileName)) { + nlohmann::json bufferObj; + auto datasetDirIt = datasetDir.end(); + --datasetDirIt; // point to the dataset directory name + bufferObj["uri"] = "../availability/" + availabilityFileName; + bufferObj["byteLength"] = nodeAvailabilityByteLengthWithPadding; + buffers.emplace_back(bufferObj); + nlohmann::json bufferViewObj; + bufferViewObj["buffer"] = bufferIndex; + bufferViewObj["byteOffset"] = 0; + bufferViewObj["byteLength"] = availabilityByteLength; + bufferViews.emplace_back(bufferViewObj); + contentObj["bufferView"] = bufferViewIndex; + bufferViewIndex += 1; + bufferIndex += 1; + } else if (csSubtreeRoots.count(subtreeRoot) != 0) { + SubtreeAvailability subtree = datasetCSSubtrees.at(dataset).at(CSKey).at(subtreeRoot); + contentObj["constant"] = static_cast(subtree.nodeCount == subtreeNodeCount); + } else + contentObj["constant"] = 0; + subtreeJson["contentAvailability"] = contentObj; + if (!buffers.empty()) + subtreeJson["buffers"] = buffers; + if (!bufferViews.empty()) + subtreeJson["bufferViews"] = bufferViews; + + // get json length + const std::string jsonString = subtreeJson.dump(); + const uint64_t jsonStringByteLength = jsonString.size(); + const uint64_t jsonStringByteLengthWithPadding = alignTo8(jsonStringByteLength); + + // Write subtree binary + uint64_t outputBufferLength = jsonStringByteLengthWithPadding + bufferByteLength + + headerByteLength; + std::vector outputBuffer(outputBufferLength); + uint8_t *outBuffer = &outputBuffer[0]; + *(uint32_t *) &outBuffer[0] = 0x74627573; // magic: "subt" + *(uint32_t *) &outBuffer[4] = 1; // version + *(uint64_t *) &outBuffer[8] = jsonStringByteLengthWithPadding; // JSON byte length with padding + *(uint64_t *) &outBuffer[16] = bufferByteLength; // BIN byte length with padding + + memcpy(&outBuffer[headerByteLength], &jsonString[0], jsonStringByteLength); + memset(&outBuffer[headerByteLength + jsonStringByteLength], + ' ', + jsonStringByteLengthWithPadding - jsonStringByteLength); + + if (bufferByteLength != 0) { + memcpy(&outBuffer[headerByteLength + jsonStringByteLengthWithPadding], + outInternalBuffer, + bufferByteLength); + } + std::filesystem::path path = datasetDir / CSKey / "subtrees" / (subtreeRoot + ".subtree"); + Utilities::writeBinaryFile(path, (const char *) outBuffer, outputBufferLength); + } + tileAndChildAvailabilities.clear(); + subtreeRoots.clear(); + } + } +} + +void CDBTilesetBuilder::initializeImplicitTilingParameters() +{ + subtreeNodeCount = static_cast((pow(4, subtreeLevels) - 1) / 3); + childSubtreeCount = static_cast(pow(4, subtreeLevels)); // 4^N + availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + nodeAvailabilityByteLengthWithPadding = alignTo8(availabilityByteLength); + childSubtreeAvailabilityByteLength = static_cast(ceil(static_cast(childSubtreeCount) / 8.0)); + childSubtreeAvailabilityByteLengthWithPadding = alignTo8(childSubtreeAvailabilityByteLength); +} + +std::string CDBTilesetBuilder::levelXYtoSubtreeKey(int level, int x, int y) +{ + return std::to_string(level) + "_" + std::to_string(x) + "_" + std::to_string(y); +} + +std::string CDBTilesetBuilder::cs1cs2ToCSKey(int cs1, int cs2) +{ + return std::to_string(cs1) + "_" + std::to_string(cs2); +} + +void CDBTilesetBuilder::addAvailability(const CDBTile &cdbTile) +{ + CDBDataset dataset = cdbTile.getDataset(); + if (datasetTilesetCollections.count(dataset) == 0) { + throw std::invalid_argument(getCDBDatasetDirectoryName(dataset) + " is not currently supported."); + } + if (datasetCSSubtrees.count(dataset) == 0) + datasetCSSubtrees.insert( + std::pair>>(dataset, + {})); + std::map> &csSubtrees = datasetCSSubtrees.at( + dataset); + + std::string csKey = cs1cs2ToCSKey(cdbTile.getCS_1(), cdbTile.getCS_2()); + if (csSubtrees.count(csKey) == 0) { + csSubtrees.insert(std::pair>( + csKey, std::map{})); + } + + std::map &subtreeMap = csSubtrees.at(csKey); + + int level = cdbTile.getLevel(); + + int x = cdbTile.getRREF(); + int y = cdbTile.getUREF(); + + SubtreeAvailability *subtree; + + if (level >= 0) { + // get the root of the subtree that this tile belongs to + int subtreeRootLevel = (level / subtreeLevels) * subtreeLevels; // the level of the subtree root + + // from Volume 1: OGC CDB Core Standard: Model and Physical Data Store Structure page 120 + int levelWithinSubtree = level - subtreeRootLevel; + int subtreeRootX = x / static_cast(glm::pow(2, levelWithinSubtree)); + int subtreeRootY = y / static_cast(glm::pow(2, levelWithinSubtree)); + + std::string subtreeKey = levelXYtoSubtreeKey(subtreeRootLevel, subtreeRootX, subtreeRootY); + if (subtreeMap.find(subtreeKey) == subtreeMap.end()) // the buffer isn't in the map + { + subtreeMap.insert( + std::pair(subtreeKey, createSubtreeAvailability())); + } + + subtree = &subtreeMap.at(subtreeKey); + + addAvailability(cdbTile, subtree, subtreeRootLevel, subtreeRootX, subtreeRootY); + } +} + +void CDBTilesetBuilder::addAvailability(const CDBTile &cdbTile, + SubtreeAvailability *subtree, + int subtreeRootLevel, + int subtreeRootX, + int subtreeRootY) +{ + if (subtree == NULL) { + throw std::invalid_argument("Subtree availability pointer is null. Check if initialized."); + } + if (subtreeLevels < 1) { + throw std::invalid_argument("Subtree level must be positive."); + } + int level = cdbTile.getLevel(); + int levelWithinSubtree = level - subtreeRootLevel; + + int localX = cdbTile.getRREF() - subtreeRootX * static_cast(pow(2, levelWithinSubtree)); + int localY = cdbTile.getUREF() - subtreeRootY * static_cast(pow(2, levelWithinSubtree)); + + setBitAtXYLevelMorton(subtree->nodeBuffer, localX, localY, levelWithinSubtree); + subtree->nodeCount += 1; + + std::string csKey = cs1cs2ToCSKey(cdbTile.getCS_1(), cdbTile.getCS_2()); + + CDBDataset tileDataset = cdbTile.getDataset(); + if (datasetCSTileAndChildAvailabilities.count(tileDataset) == 0) + datasetCSTileAndChildAvailabilities.insert( + std::pair>>( + tileDataset, std::map>{})); + std::map> &csTileAndChildAvailabilities + = datasetCSTileAndChildAvailabilities.at(tileDataset); + + if (csTileAndChildAvailabilities.count(csKey) == 0) + csTileAndChildAvailabilities.insert(std::pair>( + csKey, std::map{})); + std::map &tileAndChildAvailabilities = csTileAndChildAvailabilities.at( + csKey); + std::string subtreeKey = levelXYtoSubtreeKey(subtreeRootLevel, subtreeRootX, subtreeRootY); + createTileAndChildSubtreeAtKey(tileAndChildAvailabilities, subtreeKey); + setBitAtXYLevelMorton(tileAndChildAvailabilities.at(subtreeKey).nodeBuffer, + localX, + localY, + levelWithinSubtree); + setParentBitsRecursively(tileAndChildAvailabilities, + level, + cdbTile.getRREF(), + cdbTile.getUREF(), + subtreeRootLevel, + subtreeRootX, + subtreeRootY); +} + +bool CDBTilesetBuilder::setBitAtXYLevelMorton(std::vector &buffer, + int localX, + int localY, + int localLevel) +{ + const uint64_t mortonIndex = libmorton::morton2D_64_encode(static_cast(localX), static_cast(localY)); + // https://github.com/CesiumGS/3d-tiles/tree/3d-tiles-next/extensions/3DTILES_implicit_tiling/0.0.0#accessing-availability-bits + const uint64_t nodeCountUpToThisLevel = (static_cast(pow(4, localLevel)) - 1) / 3; + + const uint64_t index = nodeCountUpToThisLevel + mortonIndex; + const uint64_t byte = index / 8; + const uint64_t bit = index % 8; + if (byte >= buffer.size()) + throw std::invalid_argument("x, y, level coordinates too large for given buffer."); + int mask = (1 << bit); + bool bitAlreadySet = (buffer[byte] & mask) >> bit == 1; + const uint8_t availability = static_cast(1 << bit); + buffer[byte] |= availability; + return bitAlreadySet; +} + +void CDBTilesetBuilder::setParentBitsRecursively( + std::map &tileAndChildAvailabilities, + int level, + int x, + int y, + int subtreeRootLevel, + int subtreeRootX, + int subtreeRootY) +{ + if (level == 0) // we reached the root tile + return; + if (level == subtreeRootLevel) // need to set childSubtree bit of parent subtree + { + subtreeRootLevel -= subtreeLevels; + subtreeRootX /= static_cast(glm::pow(2, subtreeLevels)); + subtreeRootY /= static_cast(glm::pow(2, subtreeLevels)); + + int localChildX = x - subtreeRootX * static_cast(pow(2, subtreeLevels)); + int localChildY = y - subtreeRootY * static_cast(pow(2, subtreeLevels)); + + std::string subtreeKey = levelXYtoSubtreeKey(subtreeRootLevel, subtreeRootX, subtreeRootY); + createTileAndChildSubtreeAtKey(tileAndChildAvailabilities, subtreeKey); + setBitAtXYLevelMorton(tileAndChildAvailabilities[subtreeKey].childBuffer, localChildX, localChildY); + } else { + level -= 1; + x /= 2; + y /= 2; + std::string subtreeKey = levelXYtoSubtreeKey(subtreeRootLevel, subtreeRootX, subtreeRootY); + createTileAndChildSubtreeAtKey(tileAndChildAvailabilities, subtreeKey); + + int localLevel = level - subtreeRootLevel; + int localX = x - subtreeRootX * static_cast(pow(2, localLevel)); + int localY = y - subtreeRootY * static_cast(pow(2, localLevel)); + + bool bitAlreadySet = setBitAtXYLevelMorton(tileAndChildAvailabilities[subtreeKey].nodeBuffer, + localX, + localY, + localLevel); + if (bitAlreadySet) // cut the recursion short + return; + } + setParentBitsRecursively(tileAndChildAvailabilities, + level, + x, + y, + subtreeRootLevel, + subtreeRootX, + subtreeRootY); +} + +void CDBTilesetBuilder::addElevationToTilesetCollection(CDBElevation &elevation, + const CDB &cdb, + const std::filesystem::path &collectionOutputDirectory) +{ + const auto &cdbTile = elevation.getTile(); + auto currentImagery = cdb.getImagery(cdbTile); + auto currentRMTexture = cdb.getRMTexture(cdbTile); + auto currentRMDescriptor = cdb.getRMDescriptor(cdbTile); + + std::filesystem::path tilesetDirectory; + CDBTileset *tileset; + getTileset(cdbTile, collectionOutputDirectory, elevationTilesets, tileset, tilesetDirectory); + + if (currentImagery) { + Texture imageryTexture = createImageryTexture(*currentImagery, tilesetDirectory); + if (currentRMTexture) { + Texture featureIDTexture = createFeatureIDTexture(*currentRMTexture, tilesetDirectory); + addElevationToTileset(elevation, + &imageryTexture, + cdb, + tilesetDirectory, + *tileset, + &featureIDTexture, + currentRMDescriptor); + } else { + addElevationToTileset(elevation, &imageryTexture, cdb, tilesetDirectory, *tileset); + } + + } else { + // find parent imagery if the current one doesn't exist + Texture *parentTexture = nullptr; + auto current = CDBTile::createParentTile(cdbTile); + while (current) { + // if not in the cache, then write the image and save its name in the cache + auto it = processedParentImagery.find(*current); + if (it == processedParentImagery.end()) { + auto parentImagery = cdb.getImagery(*current); + if (parentImagery) { + auto newTexture = createImageryTexture(*parentImagery, tilesetDirectory); + auto cacheImageryTexture = processedParentImagery.insert( + {*current, std::move(newTexture)}); + + parentTexture = &(cacheImageryTexture.first->second); + + break; + } + } else { + // found it, we don't need to read the image again. Just use saved name of the saved image + parentTexture = &it->second; + break; + } + + current = CDBTile::createParentTile(*current); + } + + // we need to re-index UV of the mesh so that it is relative to the parent tile UVs for this case. + // This step is not necessary for negative LOD since the tile and the parent covers the whole geo cell + if (parentTexture && cdbTile.getLevel() > 0) { + elevation.indexUVRelativeToParent(*current); + } + + if (parentTexture) { + addElevationToTileset(elevation, parentTexture, cdb, tilesetDirectory, *tileset); + } else { + addElevationToTileset(elevation, nullptr, cdb, tilesetDirectory, *tileset); + } + } +} + +void CDBTilesetBuilder::addElevationToTileset(CDBElevation &elevation, + const Texture *imagery, + const CDB &cdb, + const std::filesystem::path &tilesetDirectory, + CDBTileset &tileset, + const Texture *featureIdTexture, + CDBRMDescriptor *materialDescriptor) +{ + const auto &mesh = elevation.getUniformGridMesh(); + if (mesh.positionRTCs.empty()) { + return; + } + + size_t targetIndexCount = static_cast(static_cast(mesh.indices.size()) + * elevationThresholdIndices); + float targetError = elevationDecimateError; + Mesh simplifed = elevation.createSimplifiedMesh(targetIndexCount, targetError); + if (simplifed.positionRTCs.empty()) { + simplifed = mesh; + } + + if (elevationNormal) { + generateElevationNormal(simplifed); + } + + CDBTile tile = elevation.getTile(); + CDBTile tileWithBoundRegion = CDBTile(tile.getGeoCell(), + tile.getDataset(), + tile.getCS_1(), + tile.getCS_2(), + tile.getLevel(), + tile.getUREF(), + tile.getRREF()); + tileWithBoundRegion.setBoundRegion( + Core::BoundingRegion(tileWithBoundRegion.getBoundRegion().getRectangle(), + elevation.getMinElevation(), + elevation.getMaxElevation())); + elevation.setTile(tileWithBoundRegion); + auto &cdbTile = elevation.getTile(); + + tinygltf::Model gltf; + // create material for mesh if there are imagery + if (imagery) { + Material material; + material.doubleSided = true; + material.unlit = !elevationNormal; + material.texture = 0; + simplifed.material = 0; + + gltf = createGltf(simplifed, &material, imagery, use3dTilesNext, featureIdTexture); + if (featureIdTexture && materialDescriptor) { + materialDescriptor->addFeatureTableToGltf(&materials, &gltf, externalSchema); + } + } else { + gltf = createGltf(simplifed, nullptr, nullptr, use3dTilesNext); + } + + if (use3dTilesNext) { + createGLTFForTileset(gltf, cdbTile, nullptr, tilesetDirectory, tileset); + } else { + createB3DMForTileset(gltf, cdbTile, nullptr, tilesetDirectory, tileset); + } + + if (cdbTile.getLevel() < 0) { + fillMissingNegativeLODElevation(elevation, cdb, tilesetDirectory, tileset); + } else { + fillMissingPositiveLODElevation(elevation, imagery, cdb, tilesetDirectory, tileset); + } +} + +void CDBTilesetBuilder::fillMissingPositiveLODElevation(const CDBElevation &elevation, + const Texture *currentImagery, + const CDB &cdb, + const std::filesystem::path &tilesetDirectory, + CDBTileset &tileset) +{ + const auto &cdbTile = elevation.getTile(); + auto nw = CDBTile::createNorthWestForPositiveLOD(cdbTile); + auto ne = CDBTile::createNorthEastForPositiveLOD(cdbTile); + auto sw = CDBTile::createSouthWestForPositiveLOD(cdbTile); + auto se = CDBTile::createSouthEastForPositiveLOD(cdbTile); + + // check if elevation exist + bool isNorthWestExist = cdb.isElevationExist(nw); + bool isNorthEastExist = cdb.isElevationExist(ne); + bool isSouthWestExist = cdb.isElevationExist(sw); + bool isSouthEastExist = cdb.isElevationExist(se); + bool shouldFillHole = isNorthEastExist || isNorthWestExist || isSouthWestExist || isSouthEastExist; + + // If we don't need to make elevation and imagery have the same LOD, then hasMoreImagery is false. + // Otherwise, check if imagery exist even the elevation has no child + bool hasMoreImagery; + if (elevationLOD) { + hasMoreImagery = false; + } else { + bool isNorthWestImageryExist = cdb.isImageryExist(nw); + bool isNorthEastImageryExist = cdb.isImageryExist(ne); + bool isSouthWestImageryExist = cdb.isImageryExist(sw); + bool isSouthEastImageryExist = cdb.isImageryExist(se); + hasMoreImagery = isNorthEastImageryExist || isNorthWestImageryExist || isSouthEastImageryExist + || isSouthWestImageryExist; + } + + if (shouldFillHole || hasMoreImagery) { + if (!isNorthWestExist) { + auto subRegionImagery = cdb.getImagery(nw); + bool reindexUV = subRegionImagery != std::nullopt; + auto subRegion = elevation.createNorthWestSubRegion(reindexUV); + if (subRegion) { + addSubRegionElevationToTileset(*subRegion, + cdb, + subRegionImagery, + currentImagery, + tilesetDirectory, + tileset); + } + } + + if (!isNorthEastExist) { + auto subRegionImagery = cdb.getImagery(ne); + bool reindexUV = subRegionImagery != std::nullopt; + auto subRegion = elevation.createNorthEastSubRegion(reindexUV); + if (subRegion) { + addSubRegionElevationToTileset(*subRegion, + cdb, + subRegionImagery, + currentImagery, + tilesetDirectory, + tileset); + } + } + + if (!isSouthEastExist) { + auto subRegionImagery = cdb.getImagery(se); + bool reindexUV = subRegionImagery != std::nullopt; + auto subRegion = elevation.createSouthEastSubRegion(reindexUV); + if (subRegion) { + addSubRegionElevationToTileset(*subRegion, + cdb, + subRegionImagery, + currentImagery, + tilesetDirectory, + tileset); + } + } + + if (!isSouthWestExist) { + auto subRegionImagery = cdb.getImagery(sw); + bool reindexUV = subRegionImagery != std::nullopt; + auto subRegion = elevation.createSouthWestSubRegion(reindexUV); + if (subRegion) { + addSubRegionElevationToTileset(*subRegion, + cdb, + subRegionImagery, + currentImagery, + tilesetDirectory, + tileset); + } + } + } +} + +void CDBTilesetBuilder::fillMissingNegativeLODElevation(CDBElevation &elevation, + const CDB &cdb, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset) +{ + const auto &cdbTile = elevation.getTile(); + auto child = CDBTile::createChildForNegativeLOD(cdbTile); + + // if imagery exist, but we have no more terrain, then duplicate it. However, + // when we only care about elevation LOD, don't duplicate it + if (!cdb.isElevationExist(child)) { + if (!elevationLOD) { + auto childImagery = cdb.getImagery(child); + if (childImagery) { + Texture imageryTexture = createImageryTexture(*childImagery, outputDirectory); + elevation.setTile(child); + addElevationToTileset(elevation, &imageryTexture, cdb, outputDirectory, tileset); + } + } + } +} + +void CDBTilesetBuilder::generateElevationNormal(Mesh &simplifed) +{ + size_t totalVertices = simplifed.positions.size(); + + // calculate normals + const auto &ellipsoid = Core::Ellipsoid::WGS84; + simplifed.normals.resize(totalVertices, glm::vec3(0.0)); + for (size_t i = 0; i < simplifed.indices.size(); i += 3) { + uint32_t idx0 = simplifed.indices[i]; + uint32_t idx1 = simplifed.indices[i + 1]; + uint32_t idx2 = simplifed.indices[i + 2]; + + glm::dvec3 p0 = simplifed.positionRTCs[idx0]; + glm::dvec3 p1 = simplifed.positionRTCs[idx1]; + glm::dvec3 p2 = simplifed.positionRTCs[idx2]; + + glm::vec3 normal = glm::cross(p1 - p0, p2 - p0); + simplifed.normals[idx0] += normal; + simplifed.normals[idx1] += normal; + simplifed.normals[idx2] += normal; + } + + // normalize normal and calculate position rtc + for (size_t i = 0; i < totalVertices; ++i) { + auto &normal = simplifed.normals[i]; + if (glm::abs(glm::dot(normal, normal)) > Core::Math::EPSILON10) { + normal = glm::normalize(normal); + } else { + auto cartographic = ellipsoid.cartesianToCartographic(simplifed.positions[i]); + if (cartographic) { + normal = ellipsoid.geodeticSurfaceNormal(*cartographic); + } + } + } +} + +void CDBTilesetBuilder::addSubRegionElevationToTileset(CDBElevation &subRegion, + const CDB &cdb, + std::optional &subRegionImagery, + const Texture *parentTexture, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset) +{ + // Use the sub region imagery. If sub region doesn't have imagery, reuse parent imagery if we don't have any higher LOD imagery + if (subRegionImagery) { + Texture subRegionTexture = createImageryTexture(*subRegionImagery, outputDirectory); + addElevationToTileset(subRegion, &subRegionTexture, cdb, outputDirectory, tileset); + } else if (parentTexture) { + addElevationToTileset(subRegion, parentTexture, cdb, outputDirectory, tileset); + } else { + addElevationToTileset(subRegion, nullptr, cdb, outputDirectory, tileset); + } +} + +Texture CDBTilesetBuilder::createFeatureIDTexture(CDBRMTexture &rmTexture, + const std::filesystem::path &tilesetOutputDirectory) const +{ + static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; + const auto &tile = rmTexture.getTile(); + auto textureRelativePath = MODEL_TEXTURE_SUB_DIR / (tile.getRelativePath().filename().string() + ".png"); + auto textureAbsolutePath = tilesetOutputDirectory / textureRelativePath; + auto textureDirectory = tilesetOutputDirectory / MODEL_TEXTURE_SUB_DIR; + if (!std::filesystem::exists(textureDirectory)) { + std::filesystem::create_directories(textureDirectory); + } + + auto driver = (GDALDriver *) GDALGetDriverByName("png"); + if (driver) { + GDALDatasetUniquePtr pngDataset = GDALDatasetUniquePtr(driver->CreateCopy(textureAbsolutePath.c_str(), + &rmTexture.getData(), + false, + nullptr, + nullptr, + nullptr)); + } + + Texture texture; + texture.uri = textureRelativePath; + texture.magFilter = TextureFilter::NEAREST; + texture.minFilter = TextureFilter::NEAREST_MIPMAP_NEAREST; + + return texture; +} + +Texture CDBTilesetBuilder::createImageryTexture(CDBImagery &imagery, + const std::filesystem::path &tilesetOutputDirectory) const +{ + static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; + + const auto &tile = imagery.getTile(); + auto textureRelativePath = MODEL_TEXTURE_SUB_DIR / (tile.getRelativePath().filename().string() + ".jpeg"); + auto textureAbsolutePath = tilesetOutputDirectory / textureRelativePath; + auto textureDirectory = tilesetOutputDirectory / MODEL_TEXTURE_SUB_DIR; + if (!std::filesystem::exists(textureDirectory)) { + std::filesystem::create_directories(textureDirectory); + } + + auto driver = (GDALDriver *) GDALGetDriverByName("jpeg"); + if (driver) { + GDALDatasetUniquePtr jpegDataset = GDALDatasetUniquePtr(driver->CreateCopy( + textureAbsolutePath.string().c_str(), &imagery.getData(), false, nullptr, nullptr, nullptr)); + } + + Texture texture; + texture.uri = textureRelativePath; + texture.magFilter = TextureFilter::LINEAR; + texture.minFilter = TextureFilter::LINEAR_MIPMAP_NEAREST; + + return texture; +} + +void CDBTilesetBuilder::addVectorToTilesetCollection( + const CDBGeometryVectors &vectors, + const std::filesystem::path &collectionOutputDirectory, + std::unordered_map &tilesetCollections) +{ + const auto &cdbTile = vectors.getTile(); + const auto &mesh = vectors.getMesh(); + if (mesh.positionRTCs.empty()) { + return; + } + + std::filesystem::path tilesetDirectory; + CDBTileset *tileset; + getTileset(cdbTile, collectionOutputDirectory, tilesetCollections, tileset, tilesetDirectory); + + tinygltf::Model gltf = createGltf(mesh, nullptr, nullptr, use3dTilesNext); + if (use3dTilesNext) { + createGLTFForTileset(gltf, cdbTile, &vectors.getInstancesAttributes(), tilesetDirectory, *tileset); + } else { + createB3DMForTileset(gltf, cdbTile, &vectors.getInstancesAttributes(), tilesetDirectory, *tileset); + } + if (use3dTilesNext && cdbTile.getLevel() >= 0) + addAvailability(cdbTile); + tileset->insertTile(cdbTile); +} + +void CDBTilesetBuilder::addGTModelToTilesetCollection(const CDBGTModels &model, + const std::filesystem::path &collectionOutputDirectory) +{ + static const std::filesystem::path MODEL_GLTF_SUB_DIR = "Gltf"; + static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; + + auto cdbTile = model.getModelsAttributes().getTile(); + + std::filesystem::path tilesetDirectory; + CDBTileset *tileset; + getTileset(cdbTile, collectionOutputDirectory, GTModelTilesets, tileset, tilesetDirectory); + + // create gltf file + auto gltfOutputDIr = tilesetDirectory / MODEL_GLTF_SUB_DIR; + std::filesystem::create_directories(gltfOutputDIr); + + std::map> instances; + const auto &modelsAttribs = model.getModelsAttributes(); + const auto &instancesAttribs = modelsAttribs.getInstancesAttributes(); + for (size_t i = 0; i < instancesAttribs.getInstancesCount(); ++i) { + std::string modelKey; + auto model3D = model.locateModel3D(i, modelKey); + if (model3D) { + if (GTModelsToGltf.find(modelKey) == GTModelsToGltf.end()) { + // write textures to files + auto textures = writeModeTextures(model3D->getTextures(), + model3D->getImages(), + MODEL_TEXTURE_SUB_DIR, + gltfOutputDIr); + + // create gltf for the instance + tinygltf::Model gltf = createGltf(model3D->getMeshes(), + model3D->getMaterials(), + textures, + use3dTilesNext); + + // write to glb + tinygltf::TinyGLTF loader; + std::filesystem::path modelGltfURI = MODEL_GLTF_SUB_DIR / (modelKey + ".glb"); + loader.WriteGltfSceneToFile(&gltf, tilesetDirectory / modelGltfURI, false, false, false, true); + GTModelsToGltf.insert({modelKey, modelGltfURI}); + } + + auto &instance = instances[modelKey]; + instance.emplace_back(i); + } + } + + std::string cdbTileFilename = cdbTile.getRelativePathWithNonZeroPaddedLevel().filename().string(); + if (use3dTilesNext) { + std::filesystem::path gltfPath = cdbTileFilename + std::string(".glb"); + std::filesystem::path gltfFullPath = tilesetDirectory / gltfPath; + + // Create glTF. + tinygltf::Model gltf; + gltf.asset.version = "2.0"; + tinygltf::Scene scene; + scene.nodes = {0}; + gltf.scenes.emplace_back(scene); + // Create buffer. + tinygltf::Buffer buffer; + gltf.buffers.emplace_back(buffer); + // Create root node. + tinygltf::Node rootNode; + rootNode.matrix = {1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1}; + gltf.nodes.emplace_back(rootNode); + // Create default sampler. + tinygltf::Sampler sampler; + sampler.magFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; + sampler.minFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; + sampler.wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; + sampler.wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; + sampler.wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; + gltf.samplers.emplace_back(sampler); + + std::string error, warning; + tinygltf::TinyGLTF io; + std::vector glbs; + + for (const auto &instance : instances) { + const auto &instanceIndices = instance.second; + tinygltf::Model loadedModel; + io.LoadBinaryFromFile(&loadedModel, + &error, + &warning, + tilesetDirectory / GTModelsToGltf[instance.first]); + + createInstancingExtension(&loadedModel, modelsAttribs, instanceIndices); + glbs.emplace_back(loadedModel); + } + + combineGltfs(&gltf, glbs); + + cdbTile.setCustomContentURI(gltfPath); + + // Enable writing textures to output folder. + auto originalPath = std::filesystem::current_path(); + std::filesystem::current_path(tilesetDirectory); + + // Create glTF stringstream + std::stringstream ss; + tinygltf::TinyGLTF gltfIO; + std::ofstream fs(gltfFullPath, std::ios::binary); + writePaddedGLB(&gltf, fs); + + std::filesystem::current_path(originalPath); + } else { + // write i3dm to cmpt + std::filesystem::path cmpt = cdbTileFilename + std::string(".cmpt"); + std::filesystem::path cmptFullPath = tilesetDirectory / cmpt; + std::ofstream fs(cmptFullPath, std::ios::binary); + auto instance = instances.begin(); + writeToCMPT(static_cast(instances.size()), fs, [&](std::ofstream &os, size_t) { + const auto &GltfURI = GTModelsToGltf[instance->first]; + const auto &instanceIndices = instance->second; + size_t totalWrite = writeToI3DM(GltfURI, modelsAttribs, instanceIndices, os); + instance = std::next(instance); + return totalWrite; + }); + + // add it to tileset + cdbTile.setCustomContentURI(cmpt); + } + if (use3dTilesNext && cdbTile.getLevel() >= 0) + addAvailability(cdbTile); + tileset->insertTile(cdbTile); +} + +void CDBTilesetBuilder::addGSModelToTilesetCollection(const CDBGSModels &model, + const std::filesystem::path &collectionOutputDirectory) +{ + static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; + + const auto &cdbTile = model.getTile(); + const auto &model3D = model.getModel3D(); + + std::filesystem::path tilesetDirectory; + CDBTileset *tileset; + getTileset(cdbTile, collectionOutputDirectory, GSModelTilesets, tileset, tilesetDirectory); + + auto textures = writeModeTextures(model3D.getTextures(), + model3D.getImages(), + MODEL_TEXTURE_SUB_DIR, + tilesetDirectory); + + auto gltf = createGltf(model3D.getMeshes(), model3D.getMaterials(), textures, use3dTilesNext); + if (use3dTilesNext) { + createGLTFForTileset(gltf, cdbTile, &model.getInstancesAttributes(), tilesetDirectory, *tileset); + } else { + createB3DMForTileset(gltf, cdbTile, &model.getInstancesAttributes(), tilesetDirectory, *tileset); + } +} + +std::vector CDBTilesetBuilder::writeModeTextures(const std::vector &modelTextures, + const std::vector> &images, + const std::filesystem::path &textureSubDir, + const std::filesystem::path &gltfPath) +{ + auto textureDirectory = gltfPath / textureSubDir; + if (!std::filesystem::exists(textureDirectory)) { + std::filesystem::create_directories(textureDirectory); + } + + auto textures = modelTextures; + for (size_t i = 0; i < modelTextures.size(); ++i) { + auto textureRelativePath = textureSubDir / modelTextures[i].uri; + auto textureAbsolutePath = gltfPath / textureSubDir / modelTextures[i].uri; + + if (processedModelTextures.find(textureAbsolutePath) == processedModelTextures.end()) { + osgDB::writeImageFile(*images[i], textureAbsolutePath.string(), nullptr); + } + + textures[i].uri = textureRelativePath.string(); + } + + return textures; +} + +void CDBTilesetBuilder::createB3DMForTileset(tinygltf::Model &gltf, + CDBTile cdbTile, + const CDBInstancesAttributes *instancesAttribs, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset) +{ + // create b3dm file + std::string cdbTileFilename = cdbTile.getRelativePathWithNonZeroPaddedLevel().filename().string(); + std::filesystem::path b3dm = cdbTileFilename + std::string(".b3dm"); + std::filesystem::path b3dmFullPath = outputDirectory / b3dm; + + // write to b3dm + std::ofstream fs(b3dmFullPath, std::ios::binary); + writeToB3DM(&gltf, instancesAttribs, fs); + cdbTile.setCustomContentURI(b3dm); + + if (use3dTilesNext) { + if (cdbTile.getLevel() >= 0) + addAvailability(cdbTile); + } + tileset.insertTile(cdbTile); +} + +void CDBTilesetBuilder::createGLTFForTileset(tinygltf::Model &gltf, + CDBTile cdbTile, + const CDBInstancesAttributes *instancesAttribs, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset) +{ + // Create glTF file + std::string cdbTileFilename = cdbTile.getRelativePathWithNonZeroPaddedLevel().filename().string(); + std::filesystem::path gltfFile = cdbTileFilename + std::string(".glb"); + std::filesystem::path gltfFullPath = outputDirectory / gltfFile; + + // Write to glTF + std::ofstream fs(gltfFullPath, std::ios::binary); + writeToGLTF(&gltf, instancesAttribs, fs); + cdbTile.setCustomContentURI(gltfFile); + + if (use3dTilesNext) { + if (cdbTile.getLevel() >= 0) + addAvailability(cdbTile); + } + tileset.insertTile(cdbTile); +} + +size_t CDBTilesetBuilder::hashComponentSelectors(int CS_1, int CS_2) +{ + size_t CSHash = 0; + hashCombine(CSHash, CS_1); + hashCombine(CSHash, CS_2); + return CSHash; +} + +std::filesystem::path CDBTilesetBuilder::getTilesetDirectory( + int CS_1, int CS_2, const std::filesystem::path &collectionOutputDirectory) +{ + return collectionOutputDirectory / (std::to_string(CS_1) + "_" + std::to_string(CS_2)); +} + +void CDBTilesetBuilder::getTileset(const CDBTile &cdbTile, + const std::filesystem::path &collectionOutputDirectory, + std::unordered_map &tilesetCollections, + CDBTileset *&tileset, + std::filesystem::path &path) +{ + const auto &geoCell = cdbTile.getGeoCell(); + auto &tilesetCollection = tilesetCollections[geoCell]; + + // find output directory + size_t CSHash = hashComponentSelectors(cdbTile.getCS_1(), cdbTile.getCS_2()); + + auto &CSToPaths = tilesetCollection.CSToPaths; + auto CSPathIt = CSToPaths.find(CSHash); + if (CSPathIt == CSToPaths.end()) { + path = getTilesetDirectory(cdbTile.getCS_1(), cdbTile.getCS_2(), collectionOutputDirectory); + std::filesystem::create_directories(path); + CSToPaths.insert({CSHash, path}); + } else { + path = CSPathIt->second; + } + + tileset = &tilesetCollection.CSToTilesets[CSHash]; +} \ No newline at end of file diff --git a/CDBTo3DTiles/src/CDBTilesetBuilder.h b/CDBTo3DTiles/src/CDBTilesetBuilder.h new file mode 100644 index 0000000..9540700 --- /dev/null +++ b/CDBTo3DTiles/src/CDBTilesetBuilder.h @@ -0,0 +1,229 @@ +#pragma once + +#include "CDB.h" +#include "CDBMaterials.h" +#include "CDBRMDescriptor.h" +#include "Gltf.h" +#include +#include + +using namespace CDBTo3DTiles; + +struct SubtreeAvailability +{ + std::vector nodeBuffer; + std::vector childBuffer; + uint64_t nodeCount = 0; + uint64_t childCount = 0; +}; + +class CDBTilesetBuilder +{ +public: + struct TilesetCollection + { + std::unordered_map CSToPaths; + std::unordered_map CSToTilesets; + }; + CDBTilesetBuilder(const std::filesystem::path &cdbInputPath, const std::filesystem::path &output) + : elevationNormal{false} + , elevationLOD{false} + , use3dTilesNext{false} + , externalSchema{false} + , subtreeLevels{7} + , elevationDecimateError{0.01f} + , elevationThresholdIndices{0.3f} + , cdbPath{cdbInputPath} + , outputPath{output} + { + if (std::filesystem::exists(output)) { + std::filesystem::remove_all(output); + } + } + + SubtreeAvailability createSubtreeAvailability() + { + SubtreeAvailability subtree; + subtree.nodeBuffer.resize(nodeAvailabilityByteLengthWithPadding); + subtree.childBuffer.resize(childSubtreeAvailabilityByteLengthWithPadding); + memset(&subtree.nodeBuffer[0], 0, nodeAvailabilityByteLengthWithPadding); + memset(&subtree.childBuffer[0], 0, childSubtreeAvailabilityByteLengthWithPadding); + return subtree; + } + + void createTileAndChildSubtreeAtKey(std::map &tileAndChildAvailabilities, + std::string subtreeKey) + { + if (tileAndChildAvailabilities.count(subtreeKey) == 0) { + tileAndChildAvailabilities.insert( + std::pair(subtreeKey, createSubtreeAvailability())); + } + } + + void flushTilesetCollection(const CDBGeoCell &geoCell, + std::unordered_map &tilesetCollections, + bool replace = true); + + void flushAvailabilitiesAndWriteSubtrees(); + + void initializeImplicitTilingParameters(); + + std::string levelXYtoSubtreeKey(int level, int x, int y); + + std::string cs1cs2ToCSKey(int cs1, int cs2); + + void addAvailability(const CDBTile &cdbTile); + + void addAvailability(const CDBTile &cdbTile, + SubtreeAvailability *subtree, + int subtreeRootLevel, + int subtreeRootX, + int subtreeRootY); + + bool setBitAtXYLevelMorton(std::vector &buffer, int localX, int localY, int localLevel = 0); + + void setParentBitsRecursively(std::map &tileAndChildAvailabilities, + int level, + int x, + int y, + int subtreeRootLevel, + int subtreeRootX, + int subtreeRootY); + + void addElevationToTilesetCollection(CDBElevation &elevation, + const CDB &cdb, + const std::filesystem::path &outputDirectory); + + void addElevationToTileset(CDBElevation &elevation, + const Texture *imagery, + const CDB &cdb, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset, + const Texture *featureIdTexture = nullptr, + CDBRMDescriptor *materialDescriptor = nullptr); + + void fillMissingPositiveLODElevation(const CDBElevation &elevation, + const Texture *currentImagery, + const CDB &cdb, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset); + + void fillMissingNegativeLODElevation(CDBElevation &elevation, + const CDB &cdb, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset); + + void addSubRegionElevationToTileset(CDBElevation &subRegion, + const CDB &cdb, + std::optional &subRegionImagery, + const Texture *parentTexture, + const std::filesystem::path &outputDirectory, + CDBTileset &tileset); + + void generateElevationNormal(Mesh &simplifed); + + Texture createImageryTexture(CDBImagery &imagery, const std::filesystem::path &tilesetDirectory) const; + Texture createFeatureIDTexture(CDBRMTexture &rmTexture, + const std::filesystem::path &tilesetOutputDirectory) const; + + void addVectorToTilesetCollection(const CDBGeometryVectors &vectors, + const std::filesystem::path &collectionOutputDirectory, + std::unordered_map &tilesetCollections); + + std::vector writeModeTextures(const std::vector &modelTextures, + const std::vector> &images, + const std::filesystem::path &textureSubDir, + const std::filesystem::path &gltfPath); + + void addGTModelToTilesetCollection(const CDBGTModels &model, const std::filesystem::path &outputDirectory); + + void addGSModelToTilesetCollection(const CDBGSModels &model, const std::filesystem::path &outputDirectory); + + void createB3DMForTileset(tinygltf::Model &model, + CDBTile cdbTile, + const CDBInstancesAttributes *instancesAttribs, + const std::filesystem::path &outputDirectory, + CDBTileset &tilesetCollections); + + void createGLTFForTileset(tinygltf::Model &model, + CDBTile cdbTile, + const CDBInstancesAttributes *instancesAttribs, + const std::filesystem::path &outputDirectory, + CDBTileset &tilesetCollections); + + size_t hashComponentSelectors(int CS_1, int CS_2); + + std::filesystem::path getTilesetDirectory(int CS_1, + int CS_2, + const std::filesystem::path &collectionOutputDirectory); + + void getTileset(const CDBTile &cdbTile, + const std::filesystem::path &outputDirectory, + std::unordered_map &tilesetCollections, + CDBTileset *&tileset, + std::filesystem::path &path); + + static const std::string ELEVATIONS_PATH; + static const std::string ROAD_NETWORK_PATH; + static const std::string RAILROAD_NETWORK_PATH; + static const std::string POWERLINE_NETWORK_PATH; + static const std::string HYDROGRAPHY_NETWORK_PATH; + static const std::string GTMODEL_PATH; + static const std::string GSMODEL_PATH; + static const std::unordered_set DATASET_PATHS; + static const int MAX_LEVEL; + + bool elevationNormal; + bool elevationLOD; + bool use3dTilesNext; + bool externalSchema; + int subtreeLevels; + uint64_t nodeAvailabilityByteLengthWithPadding; + uint64_t childSubtreeAvailabilityByteLengthWithPadding; + uint64_t subtreeNodeCount; + uint64_t childSubtreeCount; + uint64_t availabilityByteLength; + uint64_t childSubtreeAvailabilityByteLength; + const uint64_t headerByteLength = 24; + + // dataset -> "CS1_CS2" -> subtree root "level_x_y" -> subtree + std::map>> + datasetCSTileAndChildAvailabilities; + + // Dataset -> component selectors "CS1_CS2" -> subtree root "level_x_y" -> subtree + std::map>> datasetCSSubtrees; + + std::map datasetDirs; + + float elevationDecimateError; + float elevationThresholdIndices; + std::filesystem::path cdbPath; + std::filesystem::path outputPath; + std::vector defaultDatasetToCombine; + std::vector> requestedDatasetToCombine; + std::unordered_set processedModelTextures; + std::unordered_map processedParentImagery; + std::unordered_map GTModelsToGltf; + std::unordered_map elevationTilesets; + std::unordered_map roadNetworkTilesets; + std::unordered_map railRoadNetworkTilesets; + std::unordered_map powerlineNetworkTilesets; + std::unordered_map hydrographyNetworkTilesets; + std::unordered_map GTModelTilesets; + std::unordered_map GSModelTilesets; + + CDBMaterials materials; + + std::map *> datasetTilesetCollections + = {{CDBDataset::Elevation, &elevationTilesets}, + {CDBDataset::GSFeature, &GSModelTilesets}, + {CDBDataset::GSModelGeometry, &GSModelTilesets}, + {CDBDataset::GSModelTexture, &GSModelTilesets}, + {CDBDataset::GTFeature, >ModelTilesets}, + {CDBDataset::GTModelGeometry_500, >ModelTilesets}, + {CDBDataset::GTModelTexture, >ModelTilesets}, + {CDBDataset::RoadNetwork, &roadNetworkTilesets}, + {CDBDataset::RailRoadNetwork, &railRoadNetworkTilesets}, + {CDBDataset::PowerlineNetwork, &powerlineNetworkTilesets}, + {CDBDataset::HydrographyNetwork, &hydrographyNetworkTilesets}}; +}; // namespace CDBTo3DTiles \ No newline at end of file diff --git a/CDBTo3DTiles/src/CDBTo3DTiles.cpp b/CDBTo3DTiles/src/CDBTo3DTiles.cpp index 735207d..2fa79cf 100644 --- a/CDBTo3DTiles/src/CDBTo3DTiles.cpp +++ b/CDBTo3DTiles/src/CDBTo3DTiles.cpp @@ -6,662 +6,21 @@ #include "cpl_conv.h" #include "gdal.h" #include "osgDB/WriteFile" +#include +#include +#include +#include #include #include +using json = nlohmann::json; namespace CDBTo3DTiles { -struct Converter::TilesetCollection -{ - std::unordered_map CSToPaths; - std::unordered_map CSToTilesets; -}; - -struct Converter::Impl -{ - Impl(const std::filesystem::path &cdbInputPath, const std::filesystem::path &output) - : elevationNormal{false} - , elevationLOD{false} - , elevationDecimateError{0.01f} - , elevationThresholdIndices{0.3f} - , cdbPath{cdbInputPath} - , outputPath{output} - { - if (std::filesystem::exists(output)) { - std::filesystem::remove_all(output); - } - } - - void flushTilesetCollection(const CDBGeoCell &geoCell, - std::unordered_map &tilesetCollections, - bool replace = true); - - void addElevationToTilesetCollection(CDBElevation &elevation, - const CDB &cdb, - const std::filesystem::path &outputDirectory); - - void addElevationToTileset(CDBElevation &elevation, - const Texture *imagery, - const CDB &cdb, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset); - - void fillMissingPositiveLODElevation(const CDBElevation &elevation, - const Texture *currentImagery, - const CDB &cdb, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset); - - void fillMissingNegativeLODElevation(CDBElevation &elevation, - const CDB &cdb, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset); - - void addSubRegionElevationToTileset(CDBElevation &subRegion, - const CDB &cdb, - std::optional &subRegionImagery, - const Texture *parentTexture, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset); - - void generateElevationNormal(Mesh &simplifed); - - Texture createImageryTexture(CDBImagery &imagery, const std::filesystem::path &tilesetDirectory) const; - - void addVectorToTilesetCollection(const CDBGeometryVectors &vectors, - const std::filesystem::path &collectionOutputDirectory, - std::unordered_map &tilesetCollections); - - std::vector writeModeTextures(const std::vector &modelTextures, - const std::vector> &images, - const std::filesystem::path &textureSubDir, - const std::filesystem::path &gltfPath); - - void addGTModelToTilesetCollection(const CDBGTModels &model, const std::filesystem::path &outputDirectory); - - void addGSModelToTilesetCollection(const CDBGSModels &model, const std::filesystem::path &outputDirectory); - - void createB3DMForTileset(tinygltf::Model &model, - CDBTile cdbTile, - const CDBInstancesAttributes *instancesAttribs, - const std::filesystem::path &outputDirectory, - CDBTileset &tilesetCollections); - - size_t hashComponentSelectors(int CS_1, int CS_2); - - std::filesystem::path getTilesetDirectory(int CS_1, - int CS_2, - const std::filesystem::path &collectionOutputDirectory); - - void getTileset(const CDBTile &cdbTile, - const std::filesystem::path &outputDirectory, - std::unordered_map &tilesetCollections, - CDBTileset *&tileset, - std::filesystem::path &path); - - static const std::string ELEVATIONS_PATH; - static const std::string ROAD_NETWORK_PATH; - static const std::string RAILROAD_NETWORK_PATH; - static const std::string POWERLINE_NETWORK_PATH; - static const std::string HYDROGRAPHY_NETWORK_PATH; - static const std::string GTMODEL_PATH; - static const std::string GSMODEL_PATH; - static const std::unordered_set DATASET_PATHS; - - bool elevationNormal; - bool elevationLOD; - float elevationDecimateError; - float elevationThresholdIndices; - std::filesystem::path cdbPath; - std::filesystem::path outputPath; - std::vector defaultDatasetToCombine; - std::vector> requestedDatasetToCombine; - std::unordered_set processedModelTextures; - std::unordered_map processedParentImagery; - std::unordered_map GTModelsToGltf; - std::unordered_map elevationTilesets; - std::unordered_map roadNetworkTilesets; - std::unordered_map railRoadNetworkTilesets; - std::unordered_map powerlineNetworkTilesets; - std::unordered_map hydrographyNetworkTilesets; - std::unordered_map GTModelTilesets; - std::unordered_map GSModelTilesets; -}; - -const std::string Converter::Impl::ELEVATIONS_PATH = "Elevation"; -const std::string Converter::Impl::ROAD_NETWORK_PATH = "RoadNetwork"; -const std::string Converter::Impl::RAILROAD_NETWORK_PATH = "RailRoadNetwork"; -const std::string Converter::Impl::POWERLINE_NETWORK_PATH = "PowerlineNetwork"; -const std::string Converter::Impl::HYDROGRAPHY_NETWORK_PATH = "HydrographyNetwork"; -const std::string Converter::Impl::GTMODEL_PATH = "GTModels"; -const std::string Converter::Impl::GSMODEL_PATH = "GSModels"; -const std::unordered_set Converter::Impl::DATASET_PATHS = {ELEVATIONS_PATH, - ROAD_NETWORK_PATH, - RAILROAD_NETWORK_PATH, - POWERLINE_NETWORK_PATH, - HYDROGRAPHY_NETWORK_PATH, - GTMODEL_PATH, - GSMODEL_PATH}; - -void Converter::Impl::flushTilesetCollection( - const CDBGeoCell &geoCell, - std::unordered_map &tilesetCollections, - bool replace) -{ - auto geoCellCollectionIt = tilesetCollections.find(geoCell); - if (geoCellCollectionIt != tilesetCollections.end()) { - const auto &tilesetCollection = geoCellCollectionIt->second; - const auto &CSToPaths = tilesetCollection.CSToPaths; - for (const auto &CSTotileset : tilesetCollection.CSToTilesets) { - const auto &tileset = CSTotileset.second; - auto root = tileset.getRoot(); - if (!root) { - continue; - } - - auto tilesetDirectory = CSToPaths.at(CSTotileset.first); - auto tilesetJsonPath = tilesetDirectory - / (CDBTile::retrieveGeoCellDatasetFromTileName(*root) + ".json"); - - // write to tileset.json file - std::ofstream fs(tilesetJsonPath); - writeToTilesetJson(tileset, replace, fs); - - // add tileset json path to be combined later for multiple geocell - // remove the output root path to become relative path - tilesetJsonPath = std::filesystem::relative(tilesetJsonPath, outputPath); - defaultDatasetToCombine.emplace_back(tilesetJsonPath); - } - - tilesetCollections.erase(geoCell); - } -} - -void Converter::Impl::addElevationToTilesetCollection(CDBElevation &elevation, - const CDB &cdb, - const std::filesystem::path &collectionOutputDirectory) -{ - const auto &cdbTile = elevation.getTile(); - auto currentImagery = cdb.getImagery(cdbTile); - - std::filesystem::path tilesetDirectory; - CDBTileset *tileset; - getTileset(cdbTile, collectionOutputDirectory, elevationTilesets, tileset, tilesetDirectory); - - if (currentImagery) { - Texture imageryTexture = createImageryTexture(*currentImagery, tilesetDirectory); - addElevationToTileset(elevation, &imageryTexture, cdb, tilesetDirectory, *tileset); - } else { - // find parent imagery if the current one doesn't exist - Texture *parentTexture = nullptr; - auto current = CDBTile::createParentTile(cdbTile); - while (current) { - // if not in the cache, then write the image and save its name in the cache - auto it = processedParentImagery.find(*current); - if (it == processedParentImagery.end()) { - auto parentImagery = cdb.getImagery(*current); - if (parentImagery) { - auto newTexture = createImageryTexture(*parentImagery, tilesetDirectory); - auto cacheImageryTexture = processedParentImagery.insert( - {*current, std::move(newTexture)}); - - parentTexture = &(cacheImageryTexture.first->second); - - break; - } - } else { - // found it, we don't need to read the image again. Just use saved name of the saved image - parentTexture = &it->second; - break; - } - - current = CDBTile::createParentTile(*current); - } - - // we need to re-index UV of the mesh so that it is relative to the parent tile UVs for this case. - // This step is not necessary for negative LOD since the tile and the parent covers the whole geo cell - if (parentTexture && cdbTile.getLevel() > 0) { - elevation.indexUVRelativeToParent(*current); - } - - if (parentTexture) { - addElevationToTileset(elevation, parentTexture, cdb, tilesetDirectory, *tileset); - } else { - addElevationToTileset(elevation, nullptr, cdb, tilesetDirectory, *tileset); - } - } -} - -void Converter::Impl::addElevationToTileset(CDBElevation &elevation, - const Texture *imagery, - const CDB &cdb, - const std::filesystem::path &tilesetDirectory, - CDBTileset &tileset) -{ - const auto &cdbTile = elevation.getTile(); - const auto &mesh = elevation.getUniformGridMesh(); - if (mesh.positionRTCs.empty()) { - return; - } - - size_t targetIndexCount = static_cast(static_cast(mesh.indices.size()) - * elevationThresholdIndices); - float targetError = elevationDecimateError; - Mesh simplifed = elevation.createSimplifiedMesh(targetIndexCount, targetError); - if (simplifed.positionRTCs.empty()) { - simplifed = mesh; - } - - if (elevationNormal) { - generateElevationNormal(simplifed); - } - - // create material for mesh if there are imagery - if (imagery) { - Material material; - material.doubleSided = true; - material.unlit = !elevationNormal; - material.texture = 0; - simplifed.material = 0; - - tinygltf::Model gltf = createGltf(simplifed, &material, imagery); - createB3DMForTileset(gltf, cdbTile, nullptr, tilesetDirectory, tileset); - } else { - tinygltf::Model gltf = createGltf(simplifed, nullptr, nullptr); - createB3DMForTileset(gltf, cdbTile, nullptr, tilesetDirectory, tileset); - } - - if (cdbTile.getLevel() < 0) { - fillMissingNegativeLODElevation(elevation, cdb, tilesetDirectory, tileset); - } else { - fillMissingPositiveLODElevation(elevation, imagery, cdb, tilesetDirectory, tileset); - } -} - -void Converter::Impl::fillMissingPositiveLODElevation(const CDBElevation &elevation, - const Texture *currentImagery, - const CDB &cdb, - const std::filesystem::path &tilesetDirectory, - CDBTileset &tileset) -{ - const auto &cdbTile = elevation.getTile(); - auto nw = CDBTile::createNorthWestForPositiveLOD(cdbTile); - auto ne = CDBTile::createNorthEastForPositiveLOD(cdbTile); - auto sw = CDBTile::createSouthWestForPositiveLOD(cdbTile); - auto se = CDBTile::createSouthEastForPositiveLOD(cdbTile); - - // check if elevation exist - bool isNorthWestExist = cdb.isElevationExist(nw); - bool isNorthEastExist = cdb.isElevationExist(ne); - bool isSouthWestExist = cdb.isElevationExist(sw); - bool isSouthEastExist = cdb.isElevationExist(se); - bool shouldFillHole = isNorthEastExist || isNorthWestExist || isSouthWestExist || isSouthEastExist; - - // If we don't need to make elevation and imagery have the same LOD, then hasMoreImagery is false. - // Otherwise, check if imagery exist even the elevation has no child - bool hasMoreImagery; - if (elevationLOD) { - hasMoreImagery = false; - } else { - bool isNorthWestImageryExist = cdb.isImageryExist(nw); - bool isNorthEastImageryExist = cdb.isImageryExist(ne); - bool isSouthWestImageryExist = cdb.isImageryExist(sw); - bool isSouthEastImageryExist = cdb.isImageryExist(se); - hasMoreImagery = isNorthEastImageryExist || isNorthWestImageryExist || isSouthEastImageryExist - || isSouthWestImageryExist; - } - - if (shouldFillHole || hasMoreImagery) { - if (!isNorthWestExist) { - auto subRegionImagery = cdb.getImagery(nw); - bool reindexUV = subRegionImagery != std::nullopt; - auto subRegion = elevation.createNorthWestSubRegion(reindexUV); - if (subRegion) { - addSubRegionElevationToTileset(*subRegion, - cdb, - subRegionImagery, - currentImagery, - tilesetDirectory, - tileset); - } - } - - if (!isNorthEastExist) { - auto subRegionImagery = cdb.getImagery(ne); - bool reindexUV = subRegionImagery != std::nullopt; - auto subRegion = elevation.createNorthEastSubRegion(reindexUV); - if (subRegion) { - addSubRegionElevationToTileset(*subRegion, - cdb, - subRegionImagery, - currentImagery, - tilesetDirectory, - tileset); - } - } - - if (!isSouthEastExist) { - auto subRegionImagery = cdb.getImagery(se); - bool reindexUV = subRegionImagery != std::nullopt; - auto subRegion = elevation.createSouthEastSubRegion(reindexUV); - if (subRegion) { - addSubRegionElevationToTileset(*subRegion, - cdb, - subRegionImagery, - currentImagery, - tilesetDirectory, - tileset); - } - } - - if (!isSouthWestExist) { - auto subRegionImagery = cdb.getImagery(sw); - bool reindexUV = subRegionImagery != std::nullopt; - auto subRegion = elevation.createSouthWestSubRegion(reindexUV); - if (subRegion) { - addSubRegionElevationToTileset(*subRegion, - cdb, - subRegionImagery, - currentImagery, - tilesetDirectory, - tileset); - } - } - } -} - -void Converter::Impl::fillMissingNegativeLODElevation(CDBElevation &elevation, - const CDB &cdb, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset) -{ - const auto &cdbTile = elevation.getTile(); - auto child = CDBTile::createChildForNegativeLOD(cdbTile); - - // if imagery exist, but we have no more terrain, then duplicate it. However, - // when we only care about elevation LOD, don't duplicate it - if (!cdb.isElevationExist(child)) { - if (!elevationLOD) { - auto childImagery = cdb.getImagery(child); - if (childImagery) { - Texture imageryTexture = createImageryTexture(*childImagery, outputDirectory); - elevation.setTile(child); - addElevationToTileset(elevation, &imageryTexture, cdb, outputDirectory, tileset); - } - } - } -} - -void Converter::Impl::generateElevationNormal(Mesh &simplifed) -{ - size_t totalVertices = simplifed.positions.size(); - - // calculate normals - const auto &ellipsoid = Core::Ellipsoid::WGS84; - simplifed.normals.resize(totalVertices, glm::vec3(0.0)); - for (size_t i = 0; i < simplifed.indices.size(); i += 3) { - uint32_t idx0 = simplifed.indices[i]; - uint32_t idx1 = simplifed.indices[i + 1]; - uint32_t idx2 = simplifed.indices[i + 2]; - - glm::dvec3 p0 = simplifed.positionRTCs[idx0]; - glm::dvec3 p1 = simplifed.positionRTCs[idx1]; - glm::dvec3 p2 = simplifed.positionRTCs[idx2]; - - glm::vec3 normal = glm::cross(p1 - p0, p2 - p0); - simplifed.normals[idx0] += normal; - simplifed.normals[idx1] += normal; - simplifed.normals[idx2] += normal; - } - - // normalize normal and calculate position rtc - for (size_t i = 0; i < totalVertices; ++i) { - auto &normal = simplifed.normals[i]; - if (glm::abs(glm::dot(normal, normal)) > Core::Math::EPSILON10) { - normal = glm::normalize(normal); - } else { - auto cartographic = ellipsoid.cartesianToCartographic(simplifed.positions[i]); - if (cartographic) { - normal = ellipsoid.geodeticSurfaceNormal(*cartographic); - } - } - } -} - -void Converter::Impl::addSubRegionElevationToTileset(CDBElevation &subRegion, - const CDB &cdb, - std::optional &subRegionImagery, - const Texture *parentTexture, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset) -{ - // Use the sub region imagery. If sub region doesn't have imagery, reuse parent imagery if we don't have any higher LOD imagery - if (subRegionImagery) { - Texture subRegionTexture = createImageryTexture(*subRegionImagery, outputDirectory); - addElevationToTileset(subRegion, &subRegionTexture, cdb, outputDirectory, tileset); - } else if (parentTexture) { - addElevationToTileset(subRegion, parentTexture, cdb, outputDirectory, tileset); - } else { - addElevationToTileset(subRegion, nullptr, cdb, outputDirectory, tileset); - } -} - -Texture Converter::Impl::createImageryTexture(CDBImagery &imagery, - const std::filesystem::path &tilesetOutputDirectory) const -{ - static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; - - const auto &tile = imagery.getTile(); - auto textureRelativePath = MODEL_TEXTURE_SUB_DIR / (tile.getRelativePath().filename().string() + ".jpeg"); - auto textureAbsolutePath = tilesetOutputDirectory / textureRelativePath; - auto textureDirectory = tilesetOutputDirectory / MODEL_TEXTURE_SUB_DIR; - if (!std::filesystem::exists(textureDirectory)) { - std::filesystem::create_directories(textureDirectory); - } - - auto driver = (GDALDriver *) GDALGetDriverByName("jpeg"); - if (driver) { - GDALDatasetUniquePtr jpegDataset = GDALDatasetUniquePtr(driver->CreateCopy( - textureAbsolutePath.string().c_str(), &imagery.getData(), false, nullptr, nullptr, nullptr)); - } - - Texture texture; - texture.uri = textureRelativePath; - texture.magFilter = TextureFilter::LINEAR; - texture.minFilter = TextureFilter::LINEAR_MIPMAP_NEAREST; - - return texture; -} - -void Converter::Impl::addVectorToTilesetCollection( - const CDBGeometryVectors &vectors, - const std::filesystem::path &collectionOutputDirectory, - std::unordered_map &tilesetCollections) -{ - const auto &cdbTile = vectors.getTile(); - const auto &mesh = vectors.getMesh(); - if (mesh.positionRTCs.empty()) { - return; - } - - std::filesystem::path tilesetDirectory; - CDBTileset *tileset; - getTileset(cdbTile, collectionOutputDirectory, tilesetCollections, tileset, tilesetDirectory); - - tinygltf::Model gltf = createGltf(mesh, nullptr, nullptr); - createB3DMForTileset(gltf, cdbTile, &vectors.getInstancesAttributes(), tilesetDirectory, *tileset); -} - -void Converter::Impl::addGTModelToTilesetCollection(const CDBGTModels &model, - const std::filesystem::path &collectionOutputDirectory) -{ - static const std::filesystem::path MODEL_GLTF_SUB_DIR = "Gltf"; - static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; - - auto cdbTile = model.getModelsAttributes().getTile(); - - std::filesystem::path tilesetDirectory; - CDBTileset *tileset; - getTileset(cdbTile, collectionOutputDirectory, GTModelTilesets, tileset, tilesetDirectory); - - // create gltf file - auto gltfOutputDIr = tilesetDirectory / MODEL_GLTF_SUB_DIR; - std::filesystem::create_directories(gltfOutputDIr); - - std::map> instances; - const auto &modelsAttribs = model.getModelsAttributes(); - const auto &instancesAttribs = modelsAttribs.getInstancesAttributes(); - for (size_t i = 0; i < instancesAttribs.getInstancesCount(); ++i) { - std::string modelKey; - auto model3D = model.locateModel3D(i, modelKey); - if (model3D) { - if (GTModelsToGltf.find(modelKey) == GTModelsToGltf.end()) { - // write textures to files - auto textures = writeModeTextures(model3D->getTextures(), - model3D->getImages(), - MODEL_TEXTURE_SUB_DIR, - gltfOutputDIr); - - // create gltf for the instance - tinygltf::Model gltf = createGltf(model3D->getMeshes(), model3D->getMaterials(), textures); - - // write to glb - tinygltf::TinyGLTF loader; - std::filesystem::path modelGltfURI = MODEL_GLTF_SUB_DIR / (modelKey + ".glb"); - loader.WriteGltfSceneToFile(&gltf, tilesetDirectory / modelGltfURI, false, false, false, true); - GTModelsToGltf.insert({modelKey, modelGltfURI}); - } - - auto &instance = instances[modelKey]; - instance.emplace_back(i); - } - } - - // write i3dm to cmpt - std::string cdbTileFilename = cdbTile.getRelativePath().filename().string(); - std::filesystem::path cmpt = cdbTileFilename + std::string(".cmpt"); - std::filesystem::path cmptFullPath = tilesetDirectory / cmpt; - std::ofstream fs(cmptFullPath, std::ios::binary); - auto instance = instances.begin(); - writeToCMPT(static_cast(instances.size()), fs, [&](std::ofstream &os, size_t) { - const auto &GltfURI = GTModelsToGltf[instance->first]; - const auto &instanceIndices = instance->second; - size_t totalWrite = writeToI3DM(GltfURI, modelsAttribs, instanceIndices, os); - instance = std::next(instance); - return totalWrite; - }); - - // add it to tileset - cdbTile.setCustomContentURI(cmpt); - tileset->insertTile(cdbTile); -} - -void Converter::Impl::addGSModelToTilesetCollection(const CDBGSModels &model, - const std::filesystem::path &collectionOutputDirectory) -{ - static const std::filesystem::path MODEL_TEXTURE_SUB_DIR = "Textures"; - const auto &cdbTile = model.getTile(); - const auto &model3D = model.getModel3D(); - - std::filesystem::path tilesetDirectory; - CDBTileset *tileset; - getTileset(cdbTile, collectionOutputDirectory, GSModelTilesets, tileset, tilesetDirectory); - - auto textures = writeModeTextures(model3D.getTextures(), - model3D.getImages(), - MODEL_TEXTURE_SUB_DIR, - tilesetDirectory); - - auto gltf = createGltf(model3D.getMeshes(), model3D.getMaterials(), textures); - createB3DMForTileset(gltf, cdbTile, &model.getInstancesAttributes(), tilesetDirectory, *tileset); -} - -std::vector Converter::Impl::writeModeTextures(const std::vector &modelTextures, - const std::vector> &images, - const std::filesystem::path &textureSubDir, - const std::filesystem::path &gltfPath) -{ - auto textureDirectory = gltfPath / textureSubDir; - if (!std::filesystem::exists(textureDirectory)) { - std::filesystem::create_directories(textureDirectory); - } - - auto textures = modelTextures; - for (size_t i = 0; i < modelTextures.size(); ++i) { - auto textureRelativePath = textureSubDir / modelTextures[i].uri; - auto textureAbsolutePath = gltfPath / textureSubDir / modelTextures[i].uri; - - if (processedModelTextures.find(textureAbsolutePath) == processedModelTextures.end()) { - osgDB::writeImageFile(*images[i], textureAbsolutePath.string(), nullptr); - } - - textures[i].uri = textureRelativePath.string(); - } - - return textures; -} - -void Converter::Impl::createB3DMForTileset(tinygltf::Model &gltf, - CDBTile cdbTile, - const CDBInstancesAttributes *instancesAttribs, - const std::filesystem::path &outputDirectory, - CDBTileset &tileset) -{ - // create b3dm file - std::string cdbTileFilename = cdbTile.getRelativePath().filename().string(); - std::filesystem::path b3dm = cdbTileFilename + std::string(".b3dm"); - std::filesystem::path b3dmFullPath = outputDirectory / b3dm; - - // write to b3dm - std::ofstream fs(b3dmFullPath, std::ios::binary); - writeToB3DM(&gltf, instancesAttribs, fs); - cdbTile.setCustomContentURI(b3dm); - - tileset.insertTile(cdbTile); -} - -size_t Converter::Impl::hashComponentSelectors(int CS_1, int CS_2) -{ - size_t CSHash = 0; - hashCombine(CSHash, CS_1); - hashCombine(CSHash, CS_2); - return CSHash; -} - -std::filesystem::path Converter::Impl::getTilesetDirectory( - int CS_1, int CS_2, const std::filesystem::path &collectionOutputDirectory) -{ - return collectionOutputDirectory / (std::to_string(CS_1) + "_" + std::to_string(CS_2)); -} - -void Converter::Impl::getTileset(const CDBTile &cdbTile, - const std::filesystem::path &collectionOutputDirectory, - std::unordered_map &tilesetCollections, - CDBTileset *&tileset, - std::filesystem::path &path) -{ - const auto &geoCell = cdbTile.getGeoCell(); - auto &tilesetCollection = tilesetCollections[geoCell]; - - // find output directory - size_t CSHash = hashComponentSelectors(cdbTile.getCS_1(), cdbTile.getCS_2()); - - auto &CSToPaths = tilesetCollection.CSToPaths; - auto CSPathIt = CSToPaths.find(CSHash); - if (CSPathIt == CSToPaths.end()) { - path = getTilesetDirectory(cdbTile.getCS_1(), cdbTile.getCS_2(), collectionOutputDirectory); - std::filesystem::create_directories(path); - CSToPaths.insert({CSHash, path}); - } else { - path = CSPathIt->second; - } - - tileset = &tilesetCollection.CSToTilesets[CSHash]; -} +const std::string MATERIALS_SCHEMA_NAME = "materials.json"; Converter::Converter(const std::filesystem::path &CDBPath, const std::filesystem::path &outputPath) { - m_impl = std::make_unique(CDBPath, outputPath); + m_impl = std::make_unique(CDBPath, outputPath); } Converter::~Converter() noexcept {} @@ -712,6 +71,16 @@ void Converter::combineDataset(const std::vector &datasets) } } +void Converter::setUse3dTilesNext(bool use3dTilesNext) +{ + m_impl->use3dTilesNext = use3dTilesNext; +} + +void Converter::setExternalSchema(bool externalSchema) +{ + m_impl->externalSchema = externalSchema; +} + void Converter::setGenerateElevationNormal(bool elevationNormal) { m_impl->elevationNormal = elevationNormal; @@ -722,6 +91,11 @@ void Converter::setElevationLODOnly(bool elevationLOD) m_impl->elevationLOD = elevationLOD; } +void Converter::setSubtreeLevels(int subtreeLevels) +{ + m_impl->subtreeLevels = subtreeLevels; +} + void Converter::setElevationThresholdIndices(float elevationThresholdIndices) { m_impl->elevationThresholdIndices = elevationThresholdIndices; @@ -738,18 +112,53 @@ void Converter::convert() std::map> combinedTilesets; std::map> combinedTilesetsRegions; std::map aggregateTilesetsRegion; + std::map &datasetDirs = m_impl->datasetDirs; + m_impl->initializeImplicitTilingParameters(); + + std::filesystem::path materialsXMLPath = m_impl->cdbPath / "Metadata" / "Materials.xml"; + if (m_impl->use3dTilesNext) { + // Parse Materials XML to build CDBBaseMaterials index. + if (std::filesystem::exists(materialsXMLPath)) { + m_impl->materials.readBaseMaterialsFile(materialsXMLPath); + } + } cdb.forEachGeoCell([&](CDBGeoCell geoCell) { + m_impl->datasetCSSubtrees.clear(); + datasetDirs.clear(); + // create directories for converted GeoCell std::filesystem::path geoCellRelativePath = geoCell.getRelativePath(); std::filesystem::path geoCellAbsolutePath = m_impl->outputPath / geoCellRelativePath; - std::filesystem::path elevationDir = geoCellAbsolutePath / Impl::ELEVATIONS_PATH; - std::filesystem::path GTModelDir = geoCellAbsolutePath / Impl::GTMODEL_PATH; - std::filesystem::path GSModelDir = geoCellAbsolutePath / Impl::GSMODEL_PATH; - std::filesystem::path roadNetworkDir = geoCellAbsolutePath / Impl::ROAD_NETWORK_PATH; - std::filesystem::path railRoadNetworkDir = geoCellAbsolutePath / Impl::RAILROAD_NETWORK_PATH; - std::filesystem::path powerlineNetworkDir = geoCellAbsolutePath / Impl::POWERLINE_NETWORK_PATH; - std::filesystem::path hydrographyNetworkDir = geoCellAbsolutePath / Impl::HYDROGRAPHY_NETWORK_PATH; + std::filesystem::path elevationDir = geoCellAbsolutePath / CDBTilesetBuilder::ELEVATIONS_PATH; + std::filesystem::path GTModelDir = geoCellAbsolutePath / CDBTilesetBuilder::GTMODEL_PATH; + std::filesystem::path GSModelDir = geoCellAbsolutePath / CDBTilesetBuilder::GSMODEL_PATH; + std::filesystem::path roadNetworkDir = geoCellAbsolutePath / CDBTilesetBuilder::ROAD_NETWORK_PATH; + std::filesystem::path railRoadNetworkDir = geoCellAbsolutePath + / CDBTilesetBuilder::RAILROAD_NETWORK_PATH; + std::filesystem::path powerlineNetworkDir = geoCellAbsolutePath + / CDBTilesetBuilder::POWERLINE_NETWORK_PATH; + std::filesystem::path hydrographyNetworkDir = geoCellAbsolutePath + / CDBTilesetBuilder::HYDROGRAPHY_NETWORK_PATH; + datasetDirs.insert(std::pair(CDBDataset::Elevation, elevationDir)); + datasetDirs.insert(std::pair(CDBDataset::GSFeature, GSModelDir)); + datasetDirs.insert( + std::pair(CDBDataset::GSModelGeometry, GSModelDir)); + datasetDirs.insert( + std::pair(CDBDataset::GSModelTexture, GSModelDir)); + datasetDirs.insert(std::pair(CDBDataset::GTFeature, GTModelDir)); + datasetDirs.insert( + std::pair(CDBDataset::GTModelGeometry_500, GTModelDir)); + datasetDirs.insert( + std::pair(CDBDataset::GTModelTexture, GTModelDir)); + datasetDirs.insert( + std::pair(CDBDataset::RoadNetwork, roadNetworkDir)); + datasetDirs.insert( + std::pair(CDBDataset::RailRoadNetwork, railRoadNetworkDir)); + datasetDirs.insert( + std::pair(CDBDataset::PowerlineNetwork, powerlineNetworkDir)); + datasetDirs.insert(std::pair(CDBDataset::HydrographyNetwork, + hydrographyNetworkDir)); // process elevation cdb.forEachElevationTile(geoCell, [&](CDBElevation elevation) { @@ -793,13 +202,15 @@ void Converter::convert() m_impl->addGTModelToTilesetCollection(GTModel, GTModelDir); }); m_impl->flushTilesetCollection(geoCell, m_impl->GTModelTilesets); - + // process GSModel cdb.forEachGSModelTile(geoCell, [&](CDBGSModels GSModel) { m_impl->addGSModelToTilesetCollection(GSModel, GSModelDir); }); m_impl->flushTilesetCollection(geoCell, m_impl->GSModelTilesets, false); + m_impl->flushAvailabilitiesAndWriteSubtrees(); + // get the converted dataset in each geocell to be combine at the end Core::BoundingRegion geoCellRegion = CDBTile::calcBoundRegion(geoCell, -10, 0, 0); for (auto tilesetJsonPath : m_impl->defaultDatasetToCombine) { @@ -851,6 +262,11 @@ void Converter::convert() std::ofstream fs(m_impl->outputPath / combinedTilesetName); combineTilesetJson(existTilesets, regions, fs); } + + if (std::filesystem::exists(materialsXMLPath) && m_impl->externalSchema) { + std::ofstream schemaFile(m_impl->outputPath / MATERIALS_SCHEMA_NAME); + schemaFile << m_impl->materials.generateSchema(); + } } USE_OSGPLUGIN(png) diff --git a/CDBTo3DTiles/src/Gltf.cpp b/CDBTo3DTiles/src/Gltf.cpp index 4dbc0ba..38cf09e 100644 --- a/CDBTo3DTiles/src/Gltf.cpp +++ b/CDBTo3DTiles/src/Gltf.cpp @@ -5,6 +5,9 @@ #include "Gltf.h" #include "Utility.h" +#include +#include + namespace std { template<> struct hash @@ -25,6 +28,12 @@ struct hash namespace CDBTo3DTiles { +static const std::string CDB_MATERIAL_CLASS_NAME = "CDBMaterialClass"; +static const std::string CDB_MATERIAL_PROPERTY_NAME = "compositeMaterialName"; +static const std::string CDB_MATERIAL_FEATURE_TABLE_NAME = "CDBMaterialFeatureTable"; +static const std::string CDB_CLASS_NAME = "CDBClass"; +static const std::string CDB_FEATURE_TABLE_NAME = "CDBFeatureTable"; + static void createGltfTexture(const Texture &texture, tinygltf::Model &gltf, std::unordered_map *samplerCache); @@ -35,7 +44,8 @@ static size_t createGltfMesh(const Mesh &mesh, size_t rootIndex, tinygltf::Model &gltf, std::vector &bufferData, - size_t bufferOffset); + size_t bufferOffset, + bool use3dTilesNext); static int primitiveTypeToGltfMode(PrimitiveType type); @@ -52,7 +62,7 @@ static void createBufferAndAccessor(tinygltf::Model &modelGltf, static int convertToGltfFilterMode(TextureFilter mode); -tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Texture *texture) +tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Texture *texture, bool use3dTilesNext, const Texture *featureIdTexture) { static const std::filesystem::path TEXTURE_SUB_DIR = "Textures"; @@ -78,7 +88,7 @@ tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Tex // add mesh size_t bufferOffset = 0; - createGltfMesh(mesh, 0, gltf, bufferData, bufferOffset); + createGltfMesh(mesh, 0, gltf, bufferData, bufferOffset, use3dTilesNext); // add material if (material) { @@ -97,6 +107,33 @@ tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Tex createGltfMaterial(*material, gltf); } + // Add Feature ID texture from RMTexture + if (featureIdTexture) { + createGltfTexture(*featureIdTexture, gltf, nullptr); + + nlohmann::json primitiveMetadataExtension = nlohmann::json::object(); + primitiveMetadataExtension["featureIdTextures"] = { + { + { "featureTable", CDB_MATERIAL_FEATURE_TABLE_NAME }, + { "featureIds", + { + { "texture", + { + { "texCoord", 0 }, + { "index", gltf.textures.size() - 1 } + } + }, + { "channels", "r" } + } + } + } + }; + + tinygltf::Value primitiveMetadataExtensionValue; + tinygltf::ParseJsonAsValue(&primitiveMetadataExtensionValue, primitiveMetadataExtension); + gltf.meshes[0].primitives[0].extensions.insert(std::pair(std::string("EXT_feature_metadata"), primitiveMetadataExtensionValue)); + } + // add buffer to the model gltf.buffers.emplace_back(bufferGltf); @@ -110,7 +147,8 @@ tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Tex tinygltf::Model createGltf(const std::vector &meshes, const std::vector &materials, - const std::vector &textures) + const std::vector &textures, + bool use3dTilesNext) { static const std::filesystem::path TEXTURE_SUB_DIR = "Textures"; @@ -159,7 +197,7 @@ tinygltf::Model createGltf(const std::vector &meshes, bufferData.resize(totalBufferSize); size_t bufferOffset = 0; for (const auto &mesh : meshes) { - bufferOffset += createGltfMesh(mesh, 0, gltf, bufferData, bufferOffset); + bufferOffset += createGltfMesh(mesh, 0, gltf, bufferData, bufferOffset, use3dTilesNext); } // add buffer to the model @@ -249,7 +287,8 @@ size_t createGltfMesh(const Mesh &mesh, size_t rootIndex, tinygltf::Model &gltf, std::vector &bufferData, - size_t offset) + size_t offset, + bool use3dTilesNext) { std::optional aabb = mesh.aabb; glm::dvec3 center = aabb ? aabb->center() : glm::dvec3(0.0); @@ -299,8 +338,8 @@ size_t createGltfMesh(const Mesh &mesh, mesh.batchIDs.size(), TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_SCALAR); - - primitiveGltf.attributes["_BATCHID"] = static_cast(gltf.accessors.size() - 1); + std::string featureIdAttributeName = use3dTilesNext ? "_FEATURE_ID_0" : "_BATCHID"; + primitiveGltf.attributes[featureIdAttributeName] = static_cast(gltf.accessors.size() - 1); offset += nextSize; totalMeshSize += nextSize; } @@ -456,4 +495,254 @@ void createBufferAndAccessor(tinygltf::Model &modelGltf, modelGltf.bufferViews.emplace_back(bufferViewGltf); modelGltf.accessors.emplace_back(accessorGltf); } +uint createMetadataBufferView(tinygltf::Model *gltf, std::vector data) +{ + // Get glTF buffer. + auto bufferData = &gltf->buffers[0].data; + size_t bufferSize = bufferData->size(); + + // Setup bufferView. + size_t bufferViewSize = sizeof(uint8_t) * data.size(); + tinygltf::BufferView bufferView; + bufferView.buffer = 0; + bufferView.byteOffset = bufferSize; + bufferView.byteLength = bufferViewSize; + gltf->bufferViews.emplace_back(bufferView); + + // Add data to buffer. + bufferData->resize(bufferSize + bufferViewSize); + std::memcpy(bufferData->data() + bufferSize, data.data(), bufferViewSize); + + return static_cast(gltf->bufferViews.size() - 1); +} + +uint createMetadataBufferView(tinygltf::Model *gltf, std::vector> strings, size_t stringsByteLength) +{ + // Get glTF buffer. + auto bufferData = &gltf->buffers[0].data; + size_t bufferSize = bufferData->size(); + + // Setup bufferView. + size_t bufferViewSize = sizeof(uint8_t) * stringsByteLength; + tinygltf::BufferView bufferView; + bufferView.buffer = 0; + bufferView.byteOffset = bufferSize; + bufferView.byteLength = bufferViewSize; + gltf->bufferViews.emplace_back(bufferView); + + // Add data to buffer. + bufferData->resize(bufferSize + bufferViewSize); + for (auto &stringData : strings) { + std::memcpy(bufferData->data() + bufferSize, stringData.data(), stringData.size()); + bufferSize += stringData.size(); + } + + return static_cast(gltf->bufferViews.size() - 1); +} + +bool ParseJsonAsValue(tinygltf::Value *ret, const nlohmann::json &o) { + return tinygltf::ParseJsonAsValue(ret, o); +} + +/** + * This function does not provide a comprehensive merge strategy for glTFs, + * and only support the specific type of glTFs created by cdb-to-3dtiles. + * + * The following assumptions about the input glTFs are made in this function: + * - All data exists in one buffer. + * - All textures use the same sampler. + * - The root node of each glTF has one child and a Y-up to Z-up matrix. + * - All glTFs being combined have the same class in EXT_feature_metadata + * + */ +void combineGltfs(tinygltf::Model *model, std::vector glbs) { + nlohmann::json metadataExtension; + metadataExtension["schema"]["classes"] = nlohmann::json::object(); + metadataExtension["featureTables"] = nlohmann::json::object(); + + auto &bufferData = model->buffers[0].data; + size_t bufferByteLength = 0; + auto bufferViewCount = 0; + auto accessorCount = 0; + auto imageCount = 0; + auto materialCount = 0; + auto textureCount = 0; + auto meshCount = 0; + auto nodeCount = 1; + + // Iterate through GLBs + for (auto &glbModel : glbs) { + + // Copy buffer data. + bufferData.resize(bufferByteLength + roundUp(glbModel.buffers[0].data.size(), 8)); + std::memcpy(bufferData.data() + bufferByteLength, glbModel.buffers[0].data.data(), glbModel.buffers[0].data.size()); + + // Append bufferViews. + for (auto &bufferView : glbModel.bufferViews) { + // Add existing buffer's byteLength to byteOffset of each bufferView. + bufferView.byteOffset += bufferByteLength; + // Add bufferView to glTF. + model->bufferViews.emplace_back(bufferView); + } + + // Append accessors. + for (auto &accessor : glbModel.accessors) { + // Add existing bufferView count as offset to bufferView of each accessor. + accessor.bufferView += bufferViewCount; + // Add accessor to glTF. + model->accessors.emplace_back(accessor); + } + + // Append images. + for (auto &image : glbModel.images) { + // Add image to glTF. + model->images.emplace_back(image); + } + + // Append textures. + for (auto &texture : glbModel.textures) { + // Add existing image count as offset to source of each texture. + texture.source += imageCount; + // Add texture to glTF. + model->textures.emplace_back(texture); + } + + // Append materials. + for (auto &material : glbModel.materials) { + // Add existing texture count as offset to material.baseColorTexture.index. + material.pbrMetallicRoughness.baseColorTexture.index += textureCount; + // Add image to glTF. + model->materials.emplace_back(material); + } + + // Append meshes. + for (auto &mesh : glbModel.meshes) { + for (auto &primitive : mesh.primitives) { + for (auto &attribute : primitive.attributes) { + // Add existing accessor count as offset to each attribute's accessor. + attribute.second += accessorCount; + } + // Add existing accessor count as offset to each primitive's indices accessor. + primitive.indices += accessorCount; + // Add existing material count as offset to each primitive's material. + primitive.material += materialCount; + } + // Add mesh to glTF. + model->meshes.emplace_back(mesh); + } + + std::string featureTableName = std::string(CDB_FEATURE_TABLE_NAME).append(std::to_string(nodeCount)); + // Append feature tables. + if (glbModel.extensions.find("EXT_feature_metadata") != glbModel.extensions.end()) { + nlohmann::json glbMetadataExt; + tinygltf::ValueToJson(glbModel.extensions["EXT_feature_metadata"], &glbMetadataExt); + + // Add class to combined glTF, if not previously added. + if (metadataExtension["schema"]["classes"].find(CDB_CLASS_NAME) == metadataExtension["schema"]["classes"].end()) { + metadataExtension["schema"]["classes"][CDB_CLASS_NAME] = glbMetadataExt["schema"]["classes"][CDB_CLASS_NAME]; + } + + for (auto &featureTable: glbMetadataExt["featureTables"].items()) { + // Offset the bufferView count for each property in each feature table. + for (auto &property: featureTable.value()["properties"].items()) { + auto propertyBufferView = glbMetadataExt["featureTables"][featureTable.key()]["properties"][property.key()]["bufferView"].get(); + glbMetadataExt["featureTables"][featureTable.key()]["properties"][property.key()]["bufferView"] = propertyBufferView + bufferViewCount; + } + // Add feature table to combined glTF + metadataExtension["featureTables"][featureTableName] = featureTable.value(); + } + } + + // Remove root node. + glbModel.nodes.erase(glbModel.nodes.begin()); + // Append nodes. + for (auto &node : glbModel.nodes) { + // Add existing mesh count as offset to each node's mesh. + node.mesh += meshCount; + + // Handle EXT_mesh_gpu_instancing + if (node.extensions.find("EXT_mesh_gpu_instancing") != node.extensions.end()) { + + // Get existing accessors. + auto translationAccessor = node.extensions["EXT_mesh_gpu_instancing"].Get("attributes").Get("TRANSLATION").GetNumberAsInt(); + auto rotationAccessor = node.extensions["EXT_mesh_gpu_instancing"].Get("attributes").Get("ROTATION").GetNumberAsInt(); + auto scaleAccessor = node.extensions["EXT_mesh_gpu_instancing"].Get("attributes").Get("SCALE").GetNumberAsInt(); + + // Update accessors. + translationAccessor += accessorCount; + rotationAccessor += accessorCount; + scaleAccessor += accessorCount; + + // Update extension. + nlohmann::json instancingExtension; + instancingExtension["attributes"]["TRANSLATION"] = translationAccessor; + instancingExtension["attributes"]["ROTATION"] = rotationAccessor; + instancingExtension["attributes"]["SCALE"] = scaleAccessor; + + // Get existing metadata extension. + nlohmann::json nodeMetadataExtension; + tinygltf::ValueToJson(node.extensions["EXT_mesh_gpu_instancing"].Get("extensions").Get("EXT_feature_metadata"), &nodeMetadataExtension); + nodeMetadataExtension["featureIdAttributes"][0]["featureTable"] = featureTableName; + instancingExtension["extensions"]["EXT_feature_metadata"] = nodeMetadataExtension; + + tinygltf::Value instancingExtensionValue; + tinygltf::ParseJsonAsValue(&instancingExtensionValue, instancingExtension); + node.extensions.erase(node.extensions.find("EXT_mesh_gpu_instancing")); + node.extensions.insert(std::pair(std::string("EXT_mesh_gpu_instancing"), instancingExtensionValue)); + } + + model->nodes.emplace_back(node); + // Add node as child to root node. + model->nodes[0].children.emplace_back(nodeCount++); + } + + bufferByteLength = bufferData.size(); + bufferViewCount = static_cast(model->bufferViews.size()); + accessorCount = static_cast(model->accessors.size()); + imageCount = static_cast(model->images.size()); + textureCount = static_cast(model->textures.size()); + materialCount = static_cast(model->materials.size()); + meshCount = static_cast(model->meshes.size()); + } + + tinygltf::Value modelExtensionValue; + tinygltf::ParseJsonAsValue(&modelExtensionValue, metadataExtension); + model->extensions.insert(std::pair(std::string("EXT_feature_metadata"), modelExtensionValue)); + model->extensionsUsed.emplace_back("EXT_mesh_gpu_instancing"); + model->extensionsUsed.emplace_back("EXT_feature_metadata"); + model->extensionsRequired.emplace_back("EXT_mesh_gpu_instancing"); +} + +// Writes GLB and adds 0x20 (' ') characters to end of JSON chunk, resizes GLB +// length and JSON chunk length, if JSON chunk is not padded to 8 bytes. +void writePaddedGLB(tinygltf::Model *gltf, std::ofstream &fs) { + // Write GLB to stringstream. + tinygltf::TinyGLTF io; + std::stringstream glbStream; + io.WriteGltfSceneToStream(gltf, glbStream, false, true); + + // PERFORMANCE_IDEA: We're copying the whole GLB buffer here. Can we do it in-place? + std::string glbStr = glbStream.str(); + // Get length of GLB and JSON chunk. + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#binary-gltf-layout + uint32_t& glbLength = *reinterpret_cast(&glbStr[8]); + uint32_t& jsonChunkLength = *reinterpret_cast(&glbStr[12]); + // Add padding for EXT_feature_metadata + size_t binChunkOffset = 20 + jsonChunkLength; + if (binChunkOffset % 8 != 0) { + // Add padding (using spaces) to JSON chunk data. + size_t paddingByteLength = roundUp(binChunkOffset, 8) - binChunkOffset; + + // Update GLB length. + glbLength += static_cast(paddingByteLength); + + // Update JSON chunk length. + jsonChunkLength += static_cast(paddingByteLength); + + glbStr.insert(binChunkOffset, paddingByteLength, ' '); + } + // Write stream to file. + fs << glbStr; +} + } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/Gltf.h b/CDBTo3DTiles/src/Gltf.h index e4d04ca..672c93f 100644 --- a/CDBTo3DTiles/src/Gltf.h +++ b/CDBTo3DTiles/src/Gltf.h @@ -1,5 +1,7 @@ #pragma once +#define NLOHMANN_JSON_HPP +#include #include "Scene.h" #include "tiny_gltf.h" #include @@ -9,10 +11,20 @@ namespace CDBTo3DTiles { -tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Texture *texture); +tinygltf::Model createGltf(const Mesh &mesh, const Material *material, const Texture *texture, bool use3dTilesNext = false, const Texture *featureIdTexture = nullptr); tinygltf::Model createGltf(const std::vector &meshes, const std::vector &materials, - const std::vector &textures); + const std::vector &textures, + bool use3dTilesNext = false); + +void combineGltfs(tinygltf::Model *model, std::vector glbs); +void writePaddedGLB(tinygltf::Model *gltf, std::ofstream &fs); +bool ParseJsonAsValue(tinygltf::Value *ret, const nlohmann::json &o); + +uint createMetadataBufferView(tinygltf::Model *gltf, std::vector data); +uint createMetadataBufferView(tinygltf::Model *gltf, std::vector> strings, size_t stringsByteLength); + +bool ParseJsonAsValue(tinygltf::Value *ret, const nlohmann::json &o); } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/TileFormatIO.cpp b/CDBTo3DTiles/src/TileFormatIO.cpp index 667fefd..0c69be9 100644 --- a/CDBTo3DTiles/src/TileFormatIO.cpp +++ b/CDBTo3DTiles/src/TileFormatIO.cpp @@ -1,21 +1,38 @@ #include "TileFormatIO.h" #include "Ellipsoid.h" -#include "glm/gtc/matrix_access.hpp" -#include "nlohmann/json.hpp" +#include "Gltf.h" -namespace CDBTo3DTiles { +#include +#include +#include +#include +#include +#include + +#include +#include +namespace CDBTo3DTiles { static float MAX_GEOMETRIC_ERROR = 300000.0f; +static std::string CDB_CLASS_NAME = "CDBClass"; +static std::string CDB_FEATURE_TABLE_NAME = "CDBFeatureTable"; static void createBatchTable(const CDBInstancesAttributes *instancesAttribs, std::string &batchTableJson, std::vector &batchTableBuffer); -static void convertTilesetToJson(const CDBTile &tile, float geometricError, nlohmann::json &json); +static void convertTilesetToJson(const CDBTile &tile, + float geometricError, + nlohmann::json &json, + bool use3dTilesNext = false, + int subtreeLevels = 7, + int maxLevel = 0, + std::map> urisAtEachLevel = {}); void combineTilesetJson(const std::vector &tilesetJsonPaths, const std::vector ®ions, - std::ofstream &fs) + std::ofstream &fs, + bool use3dTilesNext) { nlohmann::json tilesetJson; tilesetJson["asset"] = {{"version", "1.0"}}; @@ -24,8 +41,14 @@ void combineTilesetJson(const std::vector &tilesetJsonPat tilesetJson["root"]["refine"] = "ADD"; tilesetJson["root"]["geometricError"] = MAX_GEOMETRIC_ERROR; + if (use3dTilesNext) { + tilesetJson["extensionsUsed"] = nlohmann::json::array({"3DTILES_implicit_tiling"}); + tilesetJson["extensionsRequired"] = nlohmann::json::array({"3DTILES_implicit_tiling"}); + } + auto rootChildren = nlohmann::json::array(); auto rootRegion = regions.front(); + for (size_t i = 0; i < tilesetJsonPaths.size(); ++i) { const auto &path = tilesetJsonPaths[i]; const auto &childBoundRegion = regions[i]; @@ -59,11 +82,16 @@ void combineTilesetJson(const std::vector &tilesetJsonPat rootRegion.getMinimumHeight(), rootRegion.getMaximumHeight(), }}}; - fs << tilesetJson << std::endl; } -void writeToTilesetJson(const CDBTileset &tileset, bool replace, std::ofstream &fs) +void writeToTilesetJson(const CDBTileset &tileset, + bool replace, + std::ofstream &fs, + bool use3dTilesNext, + int subtreeLevels, + int maxLevel, + std::map> urisAtEachLevel) { nlohmann::json tilesetJson; tilesetJson["asset"] = {{"version", "1.0"}}; @@ -74,14 +102,157 @@ void writeToTilesetJson(const CDBTileset &tileset, bool replace, std::ofstream & tilesetJson["root"]["refine"] = "ADD"; } + if (use3dTilesNext) { + tilesetJson["extensionsUsed"] = nlohmann::json::array({"3DTILES_implicit_tiling"}); + tilesetJson["extensionsRequired"] = nlohmann::json::array({"3DTILES_implicit_tiling"}); + } + auto root = tileset.getRoot(); if (root) { - convertTilesetToJson(*root, MAX_GEOMETRIC_ERROR, tilesetJson["root"]); + convertTilesetToJson(*root, + MAX_GEOMETRIC_ERROR, + tilesetJson["root"], + use3dTilesNext, + subtreeLevels, + maxLevel, + urisAtEachLevel); tilesetJson["geometricError"] = tilesetJson["root"]["geometricError"]; fs << tilesetJson << std::endl; } } +void createInstancingExtension(tinygltf::Model *gltf, + const CDBModelsAttributes &modelsAttribs, + const std::vector &attribIndices) +{ + const auto &cdbTile = modelsAttribs.getTile(); + const auto &instancesAttribs = modelsAttribs.getInstancesAttributes(); + const auto &cartographicPositions = modelsAttribs.getCartographicPositions(); + const auto &scales = modelsAttribs.getScales(); + const auto &orientation = modelsAttribs.getOrientations(); + + size_t totalInstances = attribIndices.size(); + + const auto &ellipsoid = Core::Ellipsoid::WGS84; + const auto tileCenterCartographic = cdbTile.getBoundRegion().getRectangle().computeCenter(); + const auto tileCenterCartesian = ellipsoid.cartographicToCartesian(tileCenterCartographic); + + // Get existing glTF buffer. + auto &bufferData = gltf->buffers[0].data; + size_t originalBufferSize = bufferData.size(); + size_t bufferSize = bufferData.size(); + + // Prepare bufferViews and accessors. + tinygltf::BufferView translationBufferView; + translationBufferView.buffer = 0; + translationBufferView.byteOffset = bufferSize; + translationBufferView.byteLength = TINYGLTF_TYPE_VEC3 * sizeof(float_t) * totalInstances; + gltf->bufferViews.emplace_back(translationBufferView); + bufferSize += translationBufferView.byteLength; + tinygltf::Accessor translationAccessor; + translationAccessor.count = totalInstances; + translationAccessor.type = TINYGLTF_TYPE_VEC3; + translationAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + translationAccessor.bufferView = static_cast(gltf->bufferViews.size() - 1); + const auto translationAccessorIndex = gltf->accessors.size(); + gltf->accessors.emplace_back(translationAccessor); + + tinygltf::BufferView rotationBufferView; + rotationBufferView.buffer = 0; + rotationBufferView.byteOffset = bufferSize; + rotationBufferView.byteLength = TINYGLTF_TYPE_VEC4 * sizeof(float_t) * totalInstances; + gltf->bufferViews.emplace_back(rotationBufferView); + bufferSize += rotationBufferView.byteLength; + tinygltf::Accessor rotationAccessor; + rotationAccessor.count = totalInstances; + rotationAccessor.type = TINYGLTF_TYPE_VEC4; + rotationAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + rotationAccessor.bufferView = static_cast(gltf->bufferViews.size() - 1); + const auto rotationAccessorIndex = gltf->accessors.size(); + gltf->accessors.emplace_back(rotationAccessor); + + tinygltf::BufferView scaleBufferView; + scaleBufferView.buffer = 0; + scaleBufferView.byteOffset = bufferSize; + scaleBufferView.byteLength = TINYGLTF_TYPE_VEC3 * sizeof(float_t) * totalInstances; + gltf->bufferViews.emplace_back(scaleBufferView); + bufferSize += scaleBufferView.byteLength; + tinygltf::Accessor scaleAccessor; + scaleAccessor.count = totalInstances; + scaleAccessor.type = TINYGLTF_TYPE_VEC3; + scaleAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + scaleAccessor.bufferView = static_cast(gltf->bufferViews.size() - 1); + const auto scaleAccessorIndex = gltf->accessors.size(); + gltf->accessors.emplace_back(scaleAccessor); + + bufferData.resize(bufferSize); + + // Iterate through instances. + for (size_t i = 0; i < totalInstances; ++i) { + size_t instanceIndex = static_cast(attribIndices[i]); + glm::dvec3 positionCartesian = ellipsoid.cartographicToCartesian(cartographicPositions[instanceIndex]); + glm::fvec3 rtcPositionCartesian = glm::fvec3(positionCartesian - tileCenterCartesian); + + glm::fmat4 tMatrix = glm::translate(glm::mat4(1.0f), rtcPositionCartesian); + glm::fmat4 rMatrix = calculateModelOrientation(positionCartesian, orientation[instanceIndex]); + rMatrix[3] = {0.0f, 0.0f, 0.0f, 1.0f}; + glm::fmat4 sMatrix = glm::scale(scales[instanceIndex]); + // In 3D Tiles 1.0, a primitive's POSITION values are written with respect to the center of the mesh, + // which is stored in node.translation. We need to apply the translation to the instance matrix + // stored in EXT_mesh_gpu_instancing. + glm::fmat4 nodeMatrix = glm::translate(glm::mat4(1.0f), + {gltf->nodes[1].translation[0], + gltf->nodes[1].translation[1], + gltf->nodes[1].translation[2]}); + glm::fmat4 instanceMatrix = tMatrix * rMatrix * sMatrix * nodeMatrix; + + glm::vec3 scale; + glm::quat rotation; + glm::vec3 translation; + glm::vec3 skew; + glm::vec4 perspective; + glm::decompose(instanceMatrix, scale, rotation, translation, skew, perspective); + + auto translationOffset = originalBufferSize + (i * sizeof(glm::vec3)); + auto rotationOffset = originalBufferSize + translationBufferView.byteLength + (i * sizeof(glm::vec4)); + auto scaleOffset = originalBufferSize + translationBufferView.byteLength + + rotationBufferView.byteLength + (i * sizeof(glm::vec3)); + + std::memcpy(bufferData.data() + translationOffset, &translation[0], sizeof(glm::vec3)); + std::memcpy(bufferData.data() + rotationOffset, &rotation[0], sizeof(glm::vec4)); + std::memcpy(bufferData.data() + scaleOffset, &scale[0], sizeof(glm::vec3)); + } + + createFeatureMetadataExtension(gltf, &instancesAttribs); + + // Create EXT_mesh_gpu_instancing JSON. + nlohmann::json instancingExtension; + instancingExtension["attributes"]["TRANSLATION"] = translationAccessorIndex; + instancingExtension["attributes"]["ROTATION"] = rotationAccessorIndex; + instancingExtension["attributes"]["SCALE"] = scaleAccessorIndex; + + // Create EXT_feature_metadata JSON. + nlohmann::json metadataExtension; + metadataExtension["featureIdAttributes"] = { + {{"featureTable", CDB_FEATURE_TABLE_NAME}, {"featureIds", {{"constant", 0}, {"divisor", 1}}}}}; + instancingExtension["extensions"]["EXT_feature_metadata"] = metadataExtension; + + // Add EXT_mesh_gpu_instancing to mesh. + tinygltf::Value instancingExtensionValue; + CDBTo3DTiles::ParseJsonAsValue(&instancingExtensionValue, instancingExtension); + // TODO: Add test case for adding extension only to nodes that have meshes. + gltf->nodes[1].extensions.insert( + std::pair(std::string("EXT_mesh_gpu_instancing"), + instancingExtensionValue)); + gltf->extensionsUsed.emplace_back("EXT_mesh_gpu_instancing"); + gltf->extensionsRequired.emplace_back("EXT_mesh_gpu_instancing"); + + // Add RTC center to node. + gltf->nodes[1].translation[0] = tileCenterCartesian.x; + gltf->nodes[1].translation[1] = tileCenterCartesian.y; + gltf->nodes[1].translation[2] = tileCenterCartesian.z; +} + size_t writeToI3DM(std::string GltfURI, const CDBModelsAttributes &modelsAttribs, const std::vector &attribIndices, @@ -287,6 +458,30 @@ void writeToB3DM(tinygltf::Model *gltf, const CDBInstancesAttributes *instancesA fs.write(reinterpret_cast(glbBuffer.data()), static_cast(glbBuffer.size())); } +void writeToGLTF(tinygltf::Model *gltf, const CDBInstancesAttributes *instancesAttribs, std::ofstream &fs) +{ + // Add metadata. + if (instancesAttribs) { + createFeatureMetadataExtension(gltf, instancesAttribs); + for (size_t i = 0; i < gltf->meshes.size(); i++) { + // Add feature ID attributes to mesh.primitive + nlohmann::json primitiveExtension; + primitiveExtension["featureIdAttributes"] = { + {{"featureTable", CDB_FEATURE_TABLE_NAME}, {"featureIds", {{"attribute", "_FEATURE_ID_0"}}}} + + }; + + tinygltf::Value primitiveExtensionValue; + CDBTo3DTiles::ParseJsonAsValue(&primitiveExtensionValue, primitiveExtension); + gltf->meshes[i].primitives[0].extensions.insert( + std::pair(std::string("EXT_feature_metadata"), + primitiveExtensionValue)); + } + } + + writePaddedGLB(gltf, fs); +} + void writeToCMPT(uint32_t numOfTiles, std::ofstream &fs, std::function writeToTileFormat) @@ -361,7 +556,166 @@ void createBatchTable(const CDBInstancesAttributes *instancesAttribs, } } -void convertTilesetToJson(const CDBTile &tile, float geometricError, nlohmann::json &json) +void createFeatureMetadataExtension(tinygltf::Model *gltf, const CDBInstancesAttributes *instancesAttribs) +{ + CDBAttributes attributes; + + // Add properties to CDB metadata class + nlohmann::json metadataExtension; + + // Add data to buffer + auto &metadataBufferData = gltf->buffers[0].data; + + size_t instanceCount = instancesAttribs->getInstancesCount(); + const auto &integerAttributes = instancesAttribs->getIntegerAttribs(); + const auto &doubleAttributes = instancesAttribs->getDoubleAttribs(); + const auto &stringAttributes = instancesAttribs->getStringAttribs(); + + // Add padding if not padded to 8 bytes, as required for FLOAT64. + if (metadataBufferData.size() % 8 != 0) { + metadataBufferData.resize(roundUp(metadataBufferData.size(), 8)); + } + + for (const auto &property : doubleAttributes) { + // Get size of metadata in bytes. + size_t propertyBufferLength = sizeof(double_t) * instanceCount; + // Resize metadata buffer. + size_t originalBufferLength = metadataBufferData.size(); + metadataBufferData.resize(metadataBufferData.size() + propertyBufferLength); + // Copy metadata into buffer. + std::memcpy(metadataBufferData.data() + originalBufferLength, + property.second.data(), + propertyBufferLength); + // Add buffer view for property + tinygltf::BufferView bufferView; + // Set the buffer to buffer.size() because buffer is added to glTF after all metadata bufferViews are added. + bufferView.buffer = 0; + bufferView.byteOffset = originalBufferLength; + bufferView.byteLength = propertyBufferLength; + gltf->bufferViews.emplace_back(bufferView); + + // Add property to class + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["name"] + = attributes.names[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["description"] + = attributes.descriptions[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["type"] + = "FLOAT64"; + + // Add propety to feature table + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["class"] = CDB_CLASS_NAME; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["count"] = instanceCount; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["properties"][property.first]["bufferView"] + = static_cast(gltf->bufferViews.size() - 1); + } + + for (const auto &property : integerAttributes) { + // Get size of metadata in bytes. + size_t propertyBufferLength = sizeof(int32_t) * instanceCount; + // Resize metadata buffer. + size_t originalBufferLength = metadataBufferData.size(); + metadataBufferData.resize(metadataBufferData.size() + propertyBufferLength); + // Copy metadata into buffer. + std::memcpy(metadataBufferData.data() + originalBufferLength, + property.second.data(), + propertyBufferLength); + // Add buffer view for property. + tinygltf::BufferView bufferView; + // Set the buffer to buffer.size() because buffer is added to glTF after all metadata bufferViews are added. + bufferView.buffer = 0; + bufferView.byteOffset = originalBufferLength; + bufferView.byteLength = propertyBufferLength; + gltf->bufferViews.emplace_back(bufferView); + + // Add property to class + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["name"] + = attributes.names[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["description"] + = attributes.descriptions[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["type"] + = "INT32"; + + // Add propety to feature table + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["class"] = CDB_CLASS_NAME; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["count"] = instanceCount; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["properties"][property.first]["bufferView"] + = static_cast(gltf->bufferViews.size() - 1); + } + + for (const auto &property : stringAttributes) { + // Add padding if not padded to 4 bytes, as required for UINT32. + if (metadataBufferData.size() % 4 != 0) { + metadataBufferData.resize(roundUp(metadataBufferData.size(), 4)); + } + + std::vector> strings; + std::vector offsets; + + uint32_t stringOffset = 0; + + for (const auto &string : property.second) { + offsets.emplace_back(stringOffset); + strings.emplace_back(std::vector(string.begin(), string.end())); + stringOffset += static_cast(string.length()); + } + offsets.emplace_back(stringOffset); + + size_t stringOffsetBufferLength = sizeof(uint32_t) * offsets.size(); + + size_t originalBufferLength = metadataBufferData.size(); + tinygltf::BufferView stringOffsetBufferView; + stringOffsetBufferView.buffer = 0; + stringOffsetBufferView.byteOffset = metadataBufferData.size(); + stringOffsetBufferView.byteLength = stringOffsetBufferLength; + gltf->bufferViews.emplace_back(stringOffsetBufferView); + metadataBufferData.resize(metadataBufferData.size() + stringOffsetBufferLength); + std::memcpy(metadataBufferData.data() + originalBufferLength, + offsets.data(), + stringOffsetBufferLength); + + originalBufferLength = metadataBufferData.size(); + tinygltf::BufferView stringBufferView; + stringBufferView.buffer = 0; + stringBufferView.byteOffset = metadataBufferData.size(); + stringBufferView.byteLength = stringOffset; + gltf->bufferViews.emplace_back(stringBufferView); + metadataBufferData.resize(metadataBufferData.size() + stringOffset); + for (auto &stringData : strings) { + std::memcpy(metadataBufferData.data() + originalBufferLength, + stringData.data(), + stringData.size()); + originalBufferLength += stringData.size(); + } + + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["name"] + = attributes.names[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["description"] + = attributes.descriptions[property.first]; + metadataExtension["schema"]["classes"][CDB_CLASS_NAME]["properties"][property.first]["type"] + = "STRING"; + + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["class"] = CDB_CLASS_NAME; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["elementCount"] = instanceCount; + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["properties"][property.first]["bufferView"] + = static_cast(gltf->bufferViews.size() - 1); + metadataExtension["featureTables"][CDB_FEATURE_TABLE_NAME]["properties"][property.first] + ["stringOffsetBufferView"] + = static_cast(gltf->bufferViews.size() - 2); + } + + tinygltf::Value metadataExtensionValue; + CDBTo3DTiles::ParseJsonAsValue(&metadataExtensionValue, metadataExtension); + gltf->extensions.insert( + std::pair(std::string("EXT_feature_metadata"), metadataExtensionValue)); + gltf->extensionsUsed.emplace_back("EXT_feature_metadata"); +} +static void convertTilesetToJson(const CDBTile &tile, + float geometricError, + nlohmann::json &json, + bool use3dTilesNext, + int subtreeLevels, + int maxLevel, + std::map> urisAtEachLevel) { const auto &boundRegion = tile.getBoundRegion(); const auto &rectangle = boundRegion.getRectangle(); @@ -374,29 +728,88 @@ void convertTilesetToJson(const CDBTile &tile, float geometricError, nlohmann::j boundRegion.getMinimumHeight(), boundRegion.getMaximumHeight(), }}}; + json["geometricError"] = geometricError; + if (use3dTilesNext) { + int level = tile.getLevel(); + if (level < 0) { + auto contentURIPath = tile.getCustomContentURI(); + if (contentURIPath) { + json["content"] = nlohmann::json::object(); + json["content"]["uri"] = *contentURIPath; + } - auto contentURI = tile.getCustomContentURI(); - if (contentURI) { - json["content"] = nlohmann::json::object(); - json["content"]["uri"] = *contentURI; + if (level == -1) // define nonnegative tiles implicitly + { + const CDBGeoCell geoCell = tile.getGeoCell(); + + nlohmann::json implicitJson = nlohmann::json::object(); + implicitJson["extensions"] = nlohmann::json::object(); + + nlohmann::json implicitTiling; + implicitTiling["maximumLevel"] = maxLevel; + implicitTiling["subdivisionScheme"] = "QUADTREE"; + implicitTiling["subtreeLevels"] = subtreeLevels; + implicitTiling["subtrees"] = nlohmann::json::object(); + std::string csKey = std::to_string(tile.getCS_1()) + "_" + std::to_string(tile.getCS_2()); + implicitTiling["subtrees"]["uri"] = "subtrees/{level}_{x}_{y}.subtree"; + + implicitJson["geometricError"] = geometricError / 2.0f; + implicitJson["boundingVolume"] = json["boundingVolume"]; + implicitJson["extensions"]["3DTILES_implicit_tiling"] = implicitTiling; + + // Replace level, x, and y with template URI + std::string contentURI = tile.getRelativePathWithNonZeroPaddedLevel().stem().string(); + std::size_t Lposition = contentURI.rfind("L"); + std::size_t underscoreAfterL = contentURI.find("_", Lposition); + contentURI.erase(Lposition + 1, underscoreAfterL - Lposition - 1); + contentURI.insert(Lposition + 1, "{level}"); + + std::size_t Uposition = contentURI.rfind("U"); + std::size_t underscoreAfterU = contentURI.find("_", Uposition); + contentURI.erase(Uposition + 1, underscoreAfterU - Uposition - 1); + contentURI.insert(Uposition + 1, "{y}"); + + std::size_t Rposition = contentURI.rfind("R"); + std::size_t dotAfterR = contentURI.find(".", Rposition); + contentURI.erase(Rposition + 1, dotAfterR - Rposition - 1); + contentURI.insert(Rposition + 1, "{x}"); + nlohmann::json content; + std::string fileExtension = ".glb"; + content["uri"] = contentURI + fileExtension; + implicitJson["content"] = content; + json["children"].emplace_back(implicitJson); + return; + } + } else + return; + } else { + auto contentURI = tile.getCustomContentURI(); + if (contentURI) { + json["content"] = nlohmann::json::object(); + json["content"]["uri"] = *contentURI; + } } - const std::vector &children = tile.getChildren(); - + const std::vector &children = tile.getChildren(); if (children.empty()) { json["geometricError"] = 0.0f; } else { - json["geometricError"] = geometricError; - for (auto child : children) { if (child == nullptr) { continue; } nlohmann::json childJson = nlohmann::json::object(); - convertTilesetToJson(*child, geometricError / 2.0f, childJson); + convertTilesetToJson(*child, + geometricError / 2.0f, + childJson, + use3dTilesNext, + subtreeLevels, + maxLevel, + urisAtEachLevel); json["children"].emplace_back(childJson); } } } + } // namespace CDBTo3DTiles diff --git a/CDBTo3DTiles/src/TileFormatIO.h b/CDBTo3DTiles/src/TileFormatIO.h index 14bb726..40e25b1 100644 --- a/CDBTo3DTiles/src/TileFormatIO.h +++ b/CDBTo3DTiles/src/TileFormatIO.h @@ -2,10 +2,12 @@ #include "CDBAttributes.h" #include "CDBTileset.h" +#include "nlohmann/json.hpp" #include "tiny_gltf.h" #include #include #include +#include #include namespace CDBTo3DTiles { @@ -43,9 +45,16 @@ struct CmptHeader void combineTilesetJson(const std::vector &tilesetJsonPaths, const std::vector ®ions, - std::ofstream &fs); + std::ofstream &fs, + bool use3dTilesNext = false); -void writeToTilesetJson(const CDBTileset &tileset, bool replace, std::ofstream &fs); +void writeToTilesetJson(const CDBTileset &tileset, + bool replace, + std::ofstream &fs, + bool use3dTilesNext = false, + int subtreeLevels = 7, + int maxLevel = 0, + std::map> urisAtEachLevel = {}); size_t writeToI3DM(std::string GltfURI, const CDBModelsAttributes &modelsAttribs, @@ -58,4 +67,12 @@ void writeToCMPT(uint32_t numOfTiles, std::ofstream &fs, std::function writeToTileFormat); +void writeToGLTF(tinygltf::Model *gltf, const CDBInstancesAttributes *instancesAttribs, std::ofstream &fs); + +void createInstancingExtension(tinygltf::Model *gltf, + const CDBModelsAttributes &modelsAttribs, + const std::vector &attribIndices); + +void createFeatureMetadataExtension(tinygltf::Model *gltf, const CDBInstancesAttributes *instancesAttribs); + } // namespace CDBTo3DTiles diff --git a/CLI/main.cpp b/CLI/main.cpp index 5df898c..be89f70 100644 --- a/CLI/main.cpp +++ b/CLI/main.cpp @@ -9,36 +9,47 @@ int main(int argc, char **argv) cxxopts::Options options("CDBConverter", "Convert CDB to 3D Tiles"); // clang-format off - options.add_options() - ("i, input", - "CDB directory", - cxxopts::value()) - ("o, output", - "3D Tiles output directory", - cxxopts::value()) - ("combine", - "Combine converted datasets into one tileset. Each dataset format is {DatasetName}_{ComponentSelector1}_{ComponentSelector2}. " - "Repeat this option to group different dataset into different tilesets. " - "E.g: --combine=Elevation_1_1,GSModels_1_1 --combine=GTModels_2_1,GTModels_1_1 will combine Elevation_1_1 and GSModels_1_1 into one tileset. GTModels_2_1 and GTModels_1_1 will be combined into a different tileset", - cxxopts::value>()->default_value("Elevation_1_1,GSModels_1_1,GTModels_2_1,GTModels_1_1")) - ("elevation-normal", - "Generate elevation normal", - cxxopts::value()->default_value("false")) - ("elevation-lod", - "Generate elevation and imagery based on elevation LOD only", - cxxopts::value()->default_value("false")) - ("elevation-decimate-error", - "Set target error when decimating elevation mesh. Target error is normalized to 0..1 (0.01 means the simplifier maintains the error to be below 1% of the mesh extents)", - cxxopts::value()->default_value("0.01")) - ("elevation-threshold-indices", - "Set target percent of indices when decimating elevation mesh", - cxxopts::value()->default_value("0.3")) - ("h, help", "Print usage"); + options.add_options("") + ("i, input", + "CDB directory", + cxxopts::value()) + ("o, output", + "3D Tiles output directory", + cxxopts::value()) + ("combine", + "Combine converted datasets into one tileset. Each dataset format is {DatasetName}_{ComponentSelector1}_{ComponentSelector2}. " + "Repeat this option to group different dataset into different tilesets. " + "E.g: --combine=Elevation_1_1,GSModels_1_1 --combine=GTModels_2_1,GTModels_1_1 will combine Elevation_1_1 and GSModels_1_1 into one tileset. GTModels_2_1 and GTModels_1_1 will be combined into a different tileset", + cxxopts::value>()->default_value("Elevation_1_1,GSModels_1_1,GTModels_2_1,GTModels_1_1")) + ("elevation-normal", + "Generate elevation normal", + cxxopts::value()->default_value("false")) + ("elevation-lod", + "Generate elevation and imagery based on elevation LOD only", + cxxopts::value()->default_value("false")) + ("elevation-decimate-error", + "Set target error when decimating elevation mesh. Target error is normalized to 0..1 (0.01 means the simplifier maintains the error to be below 1% of the mesh extents)", + cxxopts::value()->default_value("0.01")) + ("elevation-threshold-indices", + "Set target percent of indices when decimating elevation mesh", + cxxopts::value()->default_value("0.3")) + ("h, help", "Print usage"); + + options.add_options("hidden") + ("3d-tiles-next", + "Experimental convert to 3DTiles with 3DTILES_content_gltf, 3DTILES_implicit_tiling, and EXT_feature_metadata extensions.", + cxxopts::value()->default_value("false")) + ("external-schema", + "Use external schema file for Feature Metadata.", + cxxopts::value()->default_value("false")) + ("subtree-levels", + "The number of levels in each subtree for implicit tiling.", + cxxopts::value()->default_value("7")); // clang-format on auto result = options.parse(argc, argv); if (result.count("help")) { - std::cout << options.help() << "\n"; + std::cout << options.help({""}) << "\n"; return 0; } @@ -47,16 +58,22 @@ int main(int argc, char **argv) std::filesystem::path CDBPath = result["input"].as(); std::filesystem::path outputPath = result["output"].as(); + bool use3dTilesNext = result["3d-tiles-next"].as(); + bool externalSchema = result["external-schema"].as(); bool generateElevationNormal = result["elevation-normal"].as(); bool elevationLOD = result["elevation-lod"].as(); + int subtreeLevels = result["subtree-levels"].as(); float elevationDecimateError = result["elevation-decimate-error"].as(); float elevationThresholdIndices = result["elevation-threshold-indices"].as(); std::vector combinedDatasets = result["combine"].as>(); CDBTo3DTiles::GlobalInitializer initializer; CDBTo3DTiles::Converter converter(CDBPath, outputPath); + converter.setUse3dTilesNext(use3dTilesNext); + converter.setExternalSchema(externalSchema); converter.setGenerateElevationNormal(generateElevationNormal); converter.setElevationLODOnly(elevationLOD); + converter.setSubtreeLevels(subtreeLevels); converter.setElevationDecimateError(elevationDecimateError); converter.setElevationThresholdIndices(elevationThresholdIndices); for (const auto &combined : combinedDatasets) { diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index d1cd268..d9240a4 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -10,7 +10,8 @@ set(sources "src/Ray.cpp" "src/BoundingRegion.cpp" "src/GlobeRectangle.cpp" - "src/Cartographic.cpp") + "src/Cartographic.cpp" + "src/FileUtil.cpp") add_library(Core ${sources}) target_include_directories(Core diff --git a/Core/include/FileUtil.h b/Core/include/FileUtil.h new file mode 100644 index 0000000..433c531 --- /dev/null +++ b/Core/include/FileUtil.h @@ -0,0 +1,17 @@ +#pragma once + +#include +namespace fs = std::filesystem; + +namespace CDBTo3DTiles +{ + namespace Utilities + { + void writeBinaryFile(const fs::path& filename, const char* binary, uint64_t byteLength); + void writeBinaryFile(std::ostream& outputStream, const char* binary, uint64_t byteLength); + void writeStringToFile(const fs::path& filename, const std::string& string); + void readBinaryFile(const fs::path& filename, uint8_t* binary, uint64_t byteLength); + std::string readFileToString(const fs::path& filename); + size_t getDirectorySize(const fs::path& path); + } // namespace Utilities +} // namespace CDBTo3DTiles diff --git a/Core/src/FileUtil.cpp b/Core/src/FileUtil.cpp new file mode 100644 index 0000000..1e85c28 --- /dev/null +++ b/Core/src/FileUtil.cpp @@ -0,0 +1,75 @@ +#include "FileUtil.h" + +#include +#include +namespace fs = std::filesystem; + +namespace CDBTo3DTiles +{ + namespace Utilities + { + void writeBinaryFile(const fs::path& filename, const char* binary, uint64_t byteLength) + { + if (!fs::is_directory(filename.parent_path())) + { + fs::create_directories(filename.parent_path()); + } + std::ofstream outputStream(filename, std::ios_base::out | std::ios_base::binary); + outputStream.write(binary, static_cast(byteLength)); + outputStream.close(); + } + + void writeBinaryFile(std::ostream& outputStream, const char* binary, uint64_t byteLength) + { + outputStream.write(binary, static_cast(byteLength)); + } + + void readBinaryFile(const fs::path& filename, uint8_t* binary, uint64_t byteLength) + { + std::ifstream inputStream(filename, std::ios_base::in | std::ios_base::binary); + inputStream.read((char*)binary, static_cast(byteLength)); + } + + void writeStringToFile(const fs::path& filename, const std::string& string) + { + if (!fs::is_directory(filename.parent_path())) + { + fs::create_directories(filename.parent_path()); + } + std::ofstream outputStream(filename, std::ios_base::out); + outputStream << string; + outputStream.close(); + } + + std::string readFileToString(const fs::path& filename) + { + std::ifstream inputStream(filename); + inputStream.seekg (0, inputStream.end); + std::streampos fileSize = inputStream.tellg(); + inputStream.seekg (0, inputStream.beg); + + std::string fileData; + fileData.resize(static_cast(fileSize)); + inputStream.read(&fileData[0], fileSize); + return fileData; + } + + size_t getDirectorySize(const fs::path& directory) + { + size_t totalSize = 0; + + if (fs::is_directory(directory)) + { + for (auto &entry : fs::recursive_directory_iterator(directory)) + { + if (fs::is_regular_file(entry.status())) + { + totalSize += fs::file_size(entry); + } + } + } + + return totalSize; + } + } // namespace Utilities +} // namespace CDBTo3DTiles diff --git a/LICENSE.md b/LICENSE.md index 3965094..ea0b3cd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1320,3 +1320,91 @@ https://github.com/OSGeo/gdal > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > See the License for the specific language governing permissions and > limitations under the License. + +### libmorton + +https://github.com/Forceflow/libmorton + +> MIT License +> +> Copyright (c) 2016 Jeroen Baert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. + +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +### rapidxml + +http://rapidxml.sourceforge.net/ + +> 2. The MIT License +> =============================================================================== +> +> Copyright (c) 2006, 2007 Marcin Kalicinski +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +> THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +> IN THE SOFTWARE. + + +### tiny-utf8 + +https://github.com/DuffsDevice/tiny-utf8/ + +BSD 3-Clause License + +Copyright (c) 2015-2020 Jakob Riedle (DuffsDevice) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 2ead3f1..9cfcec8 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ See [Getting Started](#getting-started) for installation, build, and usage instr The [San Diego CDB](https://gsa-temp-public.s3.us-east-1.amazonaws.com/CDB_san_diego_v4.1.zip) end-user license agreement can be found [here](./Doc/SanDiego_CDB_EULA.pdf). -### :sparkles: Contributions +### :heavy_plus_sign: Contributions Pull requests are appreciated. Please use the same [Contributor License Agreement (CLA)](https://github.com/CesiumGS/cesium/blob/master/CONTRIBUTING.md) used for [CesiumJS](https://cesium.com/cesiumjs/). @@ -44,11 +44,11 @@ Power Line Network|`203_PowerLineNetwork`|:heavy_check_mark: Hydrography Network|`204_HydrographyNetwork`|:heavy_check_mark: Geotypical models|`101_GTFeature`, `500_GTModelGeometry`, `501_GTModelTexture`|:heavy_check_mark: Geospecific models|`100_GSFeature`, `300_GSModelGeometry`, `301_GSModelTexture`|:heavy_check_mark: +Raster Materials|`005_RMTexture`, `006_RMDescriptor`|:heavy_check_mark: Moving models|`600_MModelGeometry`, `601_GSModelTexture`|:x: Min Max Elevation|`002_MinMaxElevation` `003_MaxCulture`|:x: Geopolitical Boundaries|`102_GeoPolitical`|:x: Vector Materials|`200_VectorMaterial`|:x: -Raster Materials|`005_RMTexture`, `006_RMDescriptor`|:x: Navigation|`400_NavData`, `401_Navigation`|:x: Bathymetry||:x: Seasonal Imagery||:x: @@ -69,7 +69,6 @@ Clamp vector layers to the primary elevation dataset|:x: * Automatic upload to Cesium ion * Support more CDB datasets * Clamp vector layers -* Output 3D Tiles Next (for interoperability with One World Terrain Well-Formed Format) If you would like to provide feedback or accelerate the product roadmap for features you would like to see included, please contact [Shehzan Mohammed](mailto:shehzan@cesium.com). @@ -166,6 +165,7 @@ Usage: --elevation-threshold-indices arg Set target percent of indices when decimating elevation mesh (default: 0.3) + --3d-tiles-next Generate 3D Tiles Next -h, --help Print usage ``` @@ -228,6 +228,31 @@ Below is the output directory of the converted San Diego 3D Tiles:

+### :sparkles: 3D Tiles Next Support + +This converter now supports generating 3D Tiles Next. Use the `--3d-tiles-next` flag to enable this feature. The following extensions are currently supported: + +glTF extensions: + +- [EXT_feature_metadata](https://github.com/CesiumGS/glTF/blob/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata) + +3D Tiles extensions + +- [3DTILES_content_gltf](https://github.com/CesiumGS/3d-tiles/tree/3d-tiles-next/extensions/3DTILES_content_gltf) +- [3DTILES_implicit_tiling](https://github.com/CesiumGS/3d-tiles/tree/3d-tiles-next/extensions/3DTILES_implicit_tiling) + + +#### Demo + +[Live Demo of GSModels dataset](http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/feature-metadata-feb/Apps/Sandcastle/standalone.html#c=xVn9ctpIEn+VWe5qgQqIrxCfsXGdA8TBByZrSHadkHIN0oAmFiOtZmRMtvzf/r+vc/c69wL3CtczGgmJT2/iuqPyAaPuX39Md0/3qEU4DeZG12WGRaY4cMS5aRLOR+4dYaiJsmR5aU8uTDqgl933X7uVK9rlXXZdN1vdV90775cPrctjA4h+tS7ugKi7vPl6RT9eXDpXrXLlqvrmS2/006L/tStufn7zZdAqP1xV+7VB+32lf9GvDi76tNe69D4CWH90Xh20O7w7d2xL/b55uGp3H/qj7sPVL2WD1ubO5eWXo3qxdsO/PNTYkr27p86v7Ve9Om8dvfrp3fTt5Ojj7PiynD0Zs3vso3tKFsQHKxhZoFZo6Qe1lhtnTPW75TKBKSP+OFNAv43ZYx54gVtRGabjmneGGfg+YWJE5wSwNM5l4FDM2lgQY+q78y53//aqXAHcarlSK1aqxWp9VCk31J+P40wIWyqhnostdDHsuxZxOLKwwJwII9R3xtXyiDoEFtN6h//V2vphDnRFKPCdRkQAW3hNuBv4ZqjROQeyrpWrVY7q9aO8Nk0bxk3CiOH5dE4FvSfcwJaVS4uX1OkVwyfYWr4DbMqJIWzCctOAmYK6DOXySGkkzQBsodweqY59Ad8wqynF2mTmE8JzL+tG+WXluFouoErVOKrX6tV6AVXL5bIUHULZIJGy2Qqrj4VtCPcaljHjuXrZSFB7VJj2TtpiNUXsYzaT+1kHgUY5XNa7jufExwZYLIMl9LT8WIQLyrCyt6GtLEQPXZ/CgnrYQDGL/EQ2NHbpVQFH5AsIgoNgLgpIJyJo6QQEUY5AP5Rjri/sfBI4NHcnbE2Cwgdw04g5x3XvpEaWu2ApRN91HACU8vZ91hEjiEf5RYXZYxzvXTZ1/XnoNDxxA4EgcJBOKWeJOHGIKYiFpgSLwCdhJsSrzdCX+mEDBQwkQ75ayvHg9BnsiNNyHddvpNJFruTyQPUYaXLOEIikYoncyRdARwubgv8WFGy2XcdCFFR9ko4ITFLUE/cBWZR7Dl6m9e6EglIZHK7lYs9cQIpHbnTIVCDToeYdsjGzHEgfKWMBOYZwLBZCgbkC9t28A2XAoSu2ULz6+lYDNFGc7JBvbOhhk3TuwSJNYMygPjAvgIovdycnHapVHa5xjJYeMXqdN6PbVq/b+seYhUbQKUpyvXO5gOogj4+hwDPSoxMf+0uD8iF1bDcgQpBh4HkQyMTKJSsRBGGifvCY+lpFwAF40BWqcMeakTYRRNminufCXE+hGQEoDfHIDVMGSKJGyZ/Gdae9j8chbCZkgYEEqWwhTETtp8/q+aZJr2UGPp9NEu6AUa977zt7ufaZpSg37dpm2YWMmWc0TeEdsK3X7Xf2s+0zLiR9yq7dEMdxF89oXAh4wLqbTq83+PkA5z4DNe26hYmzTjcCa+aE/UC40U+yOJHgytxP0ZGQSpDC5qqMry3Lame2rIcGheuf1dGV1/ZAQV0pkaiZE5lvUCpt9x7MTVm+vSrydFWMOxyX9d2Akz7gaM/M4escOJUa0Zkvj7xpQv4Cc+T55J4CMxwlNp3ZDvyF3SjIw8xVR028GmIcqipPStBDYR6r+06eOlidVPEZLB+FHZU8at5oW5prUQMPc5ETDMIsiBEqfZXXKsjz4QcdP/rYzqUQ8/lVq+QTWGGa8zGh39vIOcpV8Vk4RVRkwwMRO6ozjU9fYyU+bcAPzeaKSCMlVNhgMG5NmA/AOjjtucDMJO40SogLR0zDdrwV0uSTbd8+76dERNsFNiOYCMifU6KrV6xaW3XqT1FoI2L267MVYz0ud0HEuwn/FA41GP3B+2Hntj/40DmU1JCOoXTVVTILcRuq85wILAcq8JMKFdl6vnYfvi/pe9BitWRb9R1JH3nqKRm/p2Q/Z7Z635OqyTYzhsyfHMzkoTLqyWm80mynez6VP6MmJHVa1T9VVMCTW8eQg9uyEfMJO8XKwGhMIBB71JM+19ipUcFIPEfNSP2svC2Qk9qpRe/B7ZjzZnRzUaRheBedkGacOTstAdlZVpsb7f76SJJeSLRz0b+OGkxidUZ44shoyp4K9W2HGnqUUdSgS6QFuGM0aA8a6JrISIHxxreQCbXKCoEK4Ck5xlJwme/OEXmA0sWlF2S1g3gwVkophmvVh4Ey/tmpsM9acu20BN/gFwA73MOs+RJxsXQIaDmFWlic4jl1lg1IXuZymfygYKv9OsFbArhIZWe1fV1rTVyUXN32t8jMohdorbZPMFjfteBBdrsmZO6JZVqLWGQ91qeva992DHkFQfwdINWzKzwnkTlnK8vO2rKYytIcr3yQ4/6mjI1oeaHkSLHK5njjXqQd+2Jl3YuEktoXiltLGKiR3bgjS77jdDRuNXTkCngixUIzG221cev5rkd8QQnPG9A+d7BpJ+6w9NNlooBES6iJnkXspwhwdUhuc944A7uk4ym7PZyyahM1yZyy4oJawm5U62XvAZ5hZPtk2sxmIxIBmQVJarp+eEnFXAYY44wMSa2TccsgEmABxJfwWbzrCTAoGRJsjS1ZvCT3AaH//v1f//nnH2kRk3VVqKUVmURU1jqJgNDURPBwRZDaqBmRc4viyCXR8wlOGcvjTHRM5PdEteKYuNZSMsnFFd++gr6BdfK0lmh156Ju1eQZE4dr4Fly8pKOznnYh50TxNehyxfqbnBjHc5uDD3dOPP2YjTONKIQXLvnVZu3+/o5FJnoC83w+i116wnpYakOgzfQp+QDBD8zf/0NFHhEp6gu793hLFHXddm/TOtTq2pl8+PM58IuprMmqqMff0QxRqWcBrFquPYSHwSplNMoa6ocvzwiR/XDKGldqmu61F/V8fH0AMoGV7Vam5i1HVzCD0iKfGFTQRRxkjbJ+Rh/f1y1aRPotu5OUlFxPTrvPl9YyKa8gaSZElfaeSx9/H1xk4Ja+cAn1ne6K+Gtpznr6sP/OYVAAZVC6eCRL1fYzoBTPDKDdPCHGDIZ0ihL1e8egNng+l/ugu43D21A/MIgrLi6iMppyVXVmMtWXj7SrpcnlgzaK1edYaqx1uYAsSrxDbTx2kt+kvU4f7Ix+a7hqxs+NFlC26PGwG8Qoqt4LKuwU9hQjscgS2BANLGD6Fzewsubg28THBaKJ0jWZkLGqBcWLJhPCLh+iu5lPwQjxLfJV7m3Jn7MwiFsCIMppCj0KfIWc+S6zgT7fcKCnN7x/EmmkDlVAXIWwf899Ih8o5ozjJKAvtQBebw0CaCZEIbJeSTutJRkVZMZtZpbXijHk9I0cJwh/UriEW2dVY9wA3CKfJUEZHblTM9+hmGcluDndk4RmreGDPbZQniNUkkPaRa5N3ituCATGPtJMeBF+aqxWDHwHH91GV7IG+i5pi7pjrYYXagUp2RSOvc8Xlr5tjTDDoz/y9J/AQ) + + + +[Live Demo of raster materials](http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/feature-id-texture-mvp/Apps/Sandcastle/standalone.html#c=zVh9c+I2E/8qKu3TkDkw76QhhCkBmpCGkAK5a3K+6QhbYDW25MeSIdxNvvuzku3EJpB2cv88MEZYu/ppd7VvtsWZkGhFyZoE6BQxskY9ImjoGR/1XN7MWfq+x5nElJHAzBXQt6fDE5NZeq2wCCOwNMIw9C0QE7KkLhFEAoOmGH5APSrpiggD23beZCi9aTTU+rNoVf6boiMUBm4rYRlyNiGCh4FFjEXAva4AxqGdrx3/0mzUQbZoCULYAsnFjD8Q1kJmjmwunfm5Rcf0cnj7dVi5pkMxZJOG1Rs2hw/+nx97l8cGMP3XPn8ApmH1+vyufO/90biflqvXn+7KV7OJdz/ryvHMebifVpz72W1j3B/VRrPl+s4b0KvepX8PYKNZtzruD8TQcx1b3989Xv99uwHe+jUrGzbrPfabveOms7y6q99dsv6gV/Ru/dXvfcfqSr68OKp/ehx3xw9/mLlImadDPapBGX6FAyTkxiXZE0sbb6rIkfks7vIAtRLDwLHYcABwODD3OTHWZzP30zebzMPl0H5CP5yeopDZZAEHbqOff0Y/fWPYI0/oFAgH496ge32g/ACcQ4HnD36s15r2sX1waOa+FN6DedX9fbAFCQd6ZB2/H7LXvep1J4Px7XQL+OzsrHlWeT/w+aQ73cZsNI/sevP9mL+NJ4PpLAu6DAhh36H/cHaXBXTp0pHLAG/eD9qd3lx0IRLG3f4rD1Df70beRm3W7Nr3OMH4ujcZzLZ9a348r+P5+2H7k7vzyfj2etsIx2dHzcZ3GOHTYLYTt4HrtVr1/bjT7jbkkruvw1UGIclwrR0qiWaLuPTwZDKd/+PUbgQE25sbSMVUEEU3pENYfhEyS6UZlD9MUo/KW1AEpC41SdLCgYR/mNV0Nu8T5fUiX28Y5XrluFouoErVOGrUGtVGAVXL5bLaOQFzYGfKli9oIywdQ/IJTGMm8o2ykeH3qbScvdzF6hZ7gNlS5dgGbGuUE0JU5ywwboAN0F8Vyvxz1UHIJkJShrXurVjfwguZBxSmNLmFUsvUJ9GntU/CCpjlsIBKJUSwkAXYa4FDFwo4dkOCqEAgJ8ozHkjnMAsdqb4XuKZg4QPIWcy8y/mDksnma7aFGXDXBUi151ufbcwXkKekvJ0olzKZwcF1gjX4Ucp9SBDwIPYh1VNwlxguX8bzJ7Ezvrijdt0zlzB7xG2S8rRUeexleIyL4fnFFVyzuLgShucuuaHWQ+RdKi5ikoOZ7W73SlNLpeqpjy0yWMHpXkRM+ajlsTCsFJGQCkIqcFiC4URiVA+Dk1Dsiuz0mqiMvTVJNVAsh3LAIfND2dXWSpnN4yvigSyx5egCLJlWKxOVPswRezz/m1ipVg0mn2EMsNUNF7pzSGJEYWZWUuj4MLMIX+wy+m8EyzAghy9en4wqU41XJHDxxtCNjWFT4cMdyGLm5i63HszcyX7uOZeSey9NaGRxw3JVqF1oI6Ii2qWKsUEfYAv/8U18lyyUWXYCPL4GSMb0QWtN2nqm05Yq0GEI4LI7o/jwW+2SVLMOXLJj5gA4bV1jSSRkWZ8EcgNtuZLSzB3q3WGh7MBPoH5i7Dm3N52UUNsuBgK9gS7CuZDARwTs8YyR9ce3EWLe9PKMPT5EBtEmUD2q8DE7PageRJ3t6YEkj7KIoWVhSRI9Oei0553ps2Tt0rwTmUxpvsP+C+h78xTELJ8gitqvTADnypbSAeKHD4fpXLxX0JeT2cb6TL8YwqUWydeyZ2JrBXcoFSj7KJ0SyKx1AVDj/CcFtKXn0xuGLUUOAKN2uT3uTRk80V3MRlcqyWXTkt4AEVeQF8u8HaeMM/K8z1PSKRT2JcnZxifGCNrzwV+j8cdBlB9NBtVCC8SjffQhCoevVSZeRBlEy4EgyXk8FEQxRpkxJR4IZHMr1NEK+8JhDVwdu+CZNl1FXplki+TR1sC+D6Hdc6hr51NgijetuuViIa6VDDo9YevBBr/Xqv9LC71m8+N8ovnwHKpcKPfxPqc7M1fewxJnrL0M0Oz5nKqoKhJ1GBCnX/5RRHhgj+qhmavHCW+HcGCOZcChF9VFVnP/WCur754VusnUfqGZ/YAUXbpXigWc1pR+jXgrzf1y8MAmgWpwQpERWX2nUD0t6KCAD5Sace7OcTAiLMzrJ+LY4VW0qlcHU3A/NPTwkgQbaI8jInQixIW010I7Ol2UvPcwkuf05948iQ8NE/3u2i4pCmjYRzMggNu/d2s9nmxT39EmTQY3V93e4J81GNHHxF4ILP1/qcxo+GdWEZN9UYGeK+TaepOOmv2Vej500+oNVN4wSpJ4EMxQe0rzECofbCxEVN909iwg6cBlJ+JHPthCFf8RQURTG2lfP0lTiyC7i31BWij5dxLLA6gygYqDr4V+8R8TeruUCNqGnIaofbrjbR3S2Qooi9B1VeCYOagKwJ9Z5nL95BGHkGJxKp2raNIwjHYJbl+vklHgpBDBeo6UfqtUigQp2mRliFpxTeaQ30gxFEX15FKsGNjDXznDa2jTuBdzl+IEX6R2UUZ+UvRWfqnr+6L0ErOlJXah992U/gc) + + + + ## Releases We release as often as needed. CDB To 3D Tiles strictly follows [semver](https://semver.org/). diff --git a/Tests/CDBElevationTest.cpp b/Tests/CDBElevationTest.cpp index a8d5b32..4a1a8b4 100644 --- a/Tests/CDBElevationTest.cpp +++ b/Tests/CDBElevationTest.cpp @@ -1,4 +1,5 @@ #include "CDBElevation.h" +#include "CDBTile.h" #include "CDBTo3DTiles.h" #include "Config.h" #include "TileFormatIO.h" @@ -32,6 +33,29 @@ static void checkAllConvertedImagery(const std::filesystem::path &imageryPath, REQUIRE(textureCount == expectedImageryCount); } +static void checkAllConvertedRMTexture(const std::filesystem::path &rmTexturePath, + const std::filesystem::path &rmTextureOutputPath, + size_t expectedRmTextureCount) +{ + size_t textureCount = 0; + for (std::filesystem::directory_entry levelDir : std::filesystem::directory_iterator(rmTexturePath)) { + for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { + for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { + auto tileFilename = tilePath.path(); + if (tileFilename.extension() != ".tif") { + continue; + } + + auto texture = rmTextureOutputPath / (tileFilename.stem().string() + ".png"); + REQUIRE(std::filesystem::exists(texture)); + ++textureCount; + } + } + } + + REQUIRE(textureCount == expectedRmTextureCount); +} + static void checkElevationDuplicated(const std::filesystem::path &imageryPath, const std::filesystem::path &elevationPath, size_t expectedElevationCount) @@ -53,7 +77,8 @@ static void checkElevationDuplicated(const std::filesystem::path &imageryPath, imageryTile->getUREF(), imageryTile->getRREF()); REQUIRE(std::filesystem::exists( - elevationPath / (elevationTile.getRelativePath().stem().string() + ".b3dm"))); + elevationPath + / (elevationTile.getRelativePathWithNonZeroPaddedLevel().stem().string() + ".b3dm"))); ++elevationCount; } @@ -532,7 +557,7 @@ TEST_CASE("Test that elevation conversion uses uniform grid mesh instead of simp converter.convert(); // check that LC09 is using uniform grid - std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC09_U0_R0.b3dm", std::ios::binary); + std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC9_U0_R0.b3dm", std::ios::binary); B3dmHeader b3dm; fs.read(reinterpret_cast(&b3dm), sizeof(b3dm)); @@ -619,3 +644,122 @@ TEST_CASE("Test that elevation conversion uses uniform grid mesh instead of simp // remove the test output std::filesystem::remove_all(output); } + +TEST_CASE("Test processing of RMTexture and RMDescriptor", "[3D Tiles Next]") +{ + std::filesystem::path input = dataPath / "ElevationWithRMTextureRMDescriptor"; + std::filesystem::path output = dataPath / "ElevationWithRMTextureRMDescriptorOutput"; + std::filesystem::path elevationOutputDir = output / "Tiles" / "N12" / "E044" / "Elevation" / "1_1"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + + const int expectedMaterialCount = 131; + + Converter converter(input, output); + converter.setUse3dTilesNext(true); + converter.convert(); + + // Check feature ID texture generation + checkAllConvertedRMTexture(input / "Tiles" / "N12" / "E044" / "005_RMTexture", textureOutputDir, 4); + + // Check feature tables creation in glTFs. + tinygltf::TinyGLTF gltfIO; + tinygltf::Model gltf; + std::string error, warning; + std::filesystem::path elevationPath = input / "Tiles" / "N12" / "E044" / "001_Elevation"; + for (std::filesystem::directory_entry levelDir : std::filesystem::directory_iterator(elevationPath)) { + for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { + for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { + auto tileFilename = tilePath.path(); + if (tileFilename.extension() != ".tif") { + continue; + } + + auto tile = CDBTile::createFromFile(tileFilename); + if (!tile) { + continue; + } + + std::filesystem::path glbPath = elevationOutputDir / (tile.value().getRelativePathWithNonZeroPaddedLevel().stem().string() + ".glb"); + REQUIRE(std::filesystem::exists(glbPath)); + + gltfIO.LoadBinaryFromFile(&gltf, &error, &warning, glbPath, 0); + + REQUIRE( + std::find(gltf.extensionsUsed.begin(), gltf.extensionsUsed.end(), "EXT_feature_metadata") + != gltf.extensionsUsed.end()); + REQUIRE(gltf.extensions.find("EXT_feature_metadata") != gltf.extensions.end()); + + // Check all enums are written. + auto enumValues = gltf.extensions["EXT_feature_metadata"].Get("schema").Get("enums").Get("CDBBaseMaterial").Get("values"); + REQUIRE(enumValues.ArrayLen() == expectedMaterialCount); + } + } + } + + // remove the test output + std::filesystem::remove_all(output); +} + +TEST_CASE("Test writing of external schema for RMDescriptor", "[3D Tiles Next]") +{ + std::filesystem::path input = dataPath / "ElevationWithRMTextureRMDescriptor"; + std::filesystem::path output = dataPath / "ElevationWithRMTextureRMDescriptorOutput"; + std::filesystem::path elevationOutputDir = output / "Tiles" / "N12" / "E044" / "Elevation" / "1_1"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + + const int expectedMaterialCount = 131; + + Converter converter(input, output); + converter.setUse3dTilesNext(true); + converter.setExternalSchema(true); + converter.convert(); + + // Check feature ID texture generation + checkAllConvertedRMTexture(input / "Tiles" / "N12" / "E044" / "005_RMTexture", textureOutputDir, 4); + + // Check feature tables creation in glTFs. + tinygltf::TinyGLTF gltfIO; + tinygltf::Model gltf; + std::string error, warning; + std::filesystem::path elevationPath = input / "Tiles" / "N12" / "E044" / "001_Elevation"; + for (std::filesystem::directory_entry levelDir : std::filesystem::directory_iterator(elevationPath)) { + for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { + for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { + auto tileFilename = tilePath.path(); + if (tileFilename.extension() != ".tif") { + continue; + } + + auto tile = CDBTile::createFromFile(tileFilename); + if (!tile) { + continue; + } + + std::filesystem::path glbPath = elevationOutputDir / (tile.value().getRelativePathWithNonZeroPaddedLevel().stem().string() + ".glb"); + REQUIRE(std::filesystem::exists(glbPath)); + + gltfIO.LoadBinaryFromFile(&gltf, &error, &warning, glbPath, 0); + + REQUIRE( + std::find(gltf.extensionsUsed.begin(), gltf.extensionsUsed.end(), "EXT_feature_metadata") + != gltf.extensionsUsed.end()); + REQUIRE(gltf.extensions.find("EXT_feature_metadata") != gltf.extensions.end()); + + // Check all enums are written. + REQUIRE(gltf.extensions["EXT_feature_metadata"].Has("schemaUri")); + } + } + } + + // Check if schema JSON is written. + std::filesystem::path materialsSchemaPath = output / "materials.json"; + REQUIRE(std::filesystem::exists(materialsSchemaPath)); + + // Check if all base materials are written. + std::ifstream schemaStream(materialsSchemaPath); + nlohmann::json materialsSchema = nlohmann::json::parse(schemaStream); + REQUIRE(materialsSchema["enums"]["CDBBaseMaterial"]["values"].size() == expectedMaterialCount); + + // remove the test output + std::filesystem::remove_all(output); +} \ No newline at end of file diff --git a/Tests/CDBGSModelsTest.cpp b/Tests/CDBGSModelsTest.cpp index df02132..00a6e92 100644 --- a/Tests/CDBGSModelsTest.cpp +++ b/Tests/CDBGSModelsTest.cpp @@ -1,8 +1,10 @@ #include "CDBModels.h" #include "CDBTo3DTiles.h" +#include "TileFormatIO.h" #include "Config.h" #include "catch2/catch.hpp" #include "nlohmann/json.hpp" +#include "tiny_gltf.h" using namespace CDBTo3DTiles; @@ -183,7 +185,7 @@ TEST_CASE("Test converting GSModel to tileset.json", "[CDBGSModels]") for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem()); REQUIRE(std::filesystem::exists( - tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); + tilesetPath / (GSModelGeometryTile->getRelativePathWithNonZeroPaddedLevel().stem().string() + ".b3dm"))); ++geometryModelCount; } } diff --git a/Tests/CDBTileTest.cpp b/Tests/CDBTileTest.cpp index 96aa151..93af385 100644 --- a/Tests/CDBTileTest.cpp +++ b/Tests/CDBTileTest.cpp @@ -497,6 +497,15 @@ TEST_CASE("Test create from file", "[CDBTile]") REQUIRE(tile->getLevel() == 3); REQUIRE(tile->getUREF() == 5); REQUIRE(tile->getRREF() == 5); + std::string path = tile->getRelativePathWithNonZeroPaddedLevel(); + + // get the level part out of the path + for(int i = 0 ; i < 5 ; i++) + { + path.erase(0, path.find("_") + 1); + } + std::string nonZeroPaddedLevel = path.substr(0, path.find("_")); + REQUIRE(nonZeroPaddedLevel == "L3"); } SECTION("Test create from valid file name with negative level") @@ -511,6 +520,15 @@ TEST_CASE("Test create from file", "[CDBTile]") REQUIRE(tile->getLevel() == -3); REQUIRE(tile->getUREF() == 0); REQUIRE(tile->getRREF() == 0); + std::string path = tile->getRelativePathWithNonZeroPaddedLevel(); + + // get the level part out of the path + for(int i = 0 ; i < 5 ; i++) + { + path.erase(0, path.find("_") + 1); + } + std::string nonZeroPaddedLevel = path.substr(0, path.find("_")); + REQUIRE(nonZeroPaddedLevel == "LC3"); } SECTION("Test create from invalid latitude") diff --git a/Tests/CDBTilesetBuilderTest.cpp b/Tests/CDBTilesetBuilderTest.cpp new file mode 100644 index 0000000..85f6969 --- /dev/null +++ b/Tests/CDBTilesetBuilderTest.cpp @@ -0,0 +1,129 @@ +#include "CDBTilesetBuilder.h" +#include "catch2/catch.hpp" +#include "morton.h" +#include "Config.h" + +using namespace CDBTo3DTiles; +using namespace Core; + +TEST_CASE("Morton index bit setting function doesn't corrupt memory.", "[CDBTilesetBuilder]") +{ + std::filesystem::path input = dataPath / "CombineTilesets"; + std::filesystem::path output = "CombineTilesets"; + std::unique_ptr builder = std::make_unique(input, output); + + std::vector dummyVector(2); + REQUIRE_THROWS_AS(builder->setBitAtXYLevelMorton(dummyVector, 4, 4), std::invalid_argument); + REQUIRE_THROWS_AS(builder->setBitAtXYLevelMorton(dummyVector, 3, 1, 3), std::invalid_argument); +} + +TEST_CASE("Test setting parent bits recursively.", "[CDBTilesetBuilder]") +{ + std::filesystem::path input = dataPath / "CombineTilesets"; + std::filesystem::path output = "CombineTilesets"; + std::unique_ptr builder = std::make_unique(input, output); + + SECTION("Test that parents of level 6 tile are set within one subtree.") + { + int subtreeLevels = 7; + builder->subtreeLevels = subtreeLevels; + uint64_t subtreeNodeCount = static_cast((pow(4, subtreeLevels)-1) / 3); + uint64_t childSubtreeCount = static_cast(pow(4, subtreeLevels)); // 4^N + uint64_t availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + uint64_t childSubtreeAvailabilityByteLength = static_cast(ceil(static_cast(childSubtreeCount) / 8.0)); + builder->nodeAvailabilityByteLengthWithPadding = availabilityByteLength; + builder->childSubtreeAvailabilityByteLengthWithPadding = childSubtreeAvailabilityByteLength; + + builder->datasetCSTileAndChildAvailabilities.insert( + std::pair>>( + CDBDataset::Elevation, + std::map>{} + ) + ); + builder->datasetCSTileAndChildAvailabilities.at(CDBDataset::Elevation).insert( + std::pair>( + "1_1", + std::map{} + ) + ); + std::map &tileAndChildAvailabilities = builder->datasetCSTileAndChildAvailabilities.at(CDBDataset::Elevation).at("1_1"); + int level = 6, x = 47, y = 61; + builder->setParentBitsRecursively(tileAndChildAvailabilities, level, x, y, 0, 0, 0); + + while(level != 0) + { + level -= 1; + x /= 2; + y /= 2; + + uint64_t mortonIndex = libmorton::morton2D_64_encode(static_cast(x), static_cast(y)); + int levelWithinSubtree = level; + const uint64_t nodeCountUpToThisLevel = ((1 << (2 * levelWithinSubtree)) - 1) / 3; + + const uint64_t index = nodeCountUpToThisLevel + mortonIndex; + uint64_t byte = index / 8; + uint64_t bit = index % 8; + // Check the bit is set + int mask = (1 << bit); + REQUIRE((tileAndChildAvailabilities["0_0_0"].nodeBuffer[byte] & mask) >> bit == 1); + } + } + + SECTION("Test that parents of level 6 tile are set, multi subtree.") + { + int subtreeLevels = 6; + builder->subtreeLevels = subtreeLevels; + uint64_t subtreeNodeCount = static_cast((pow(4, subtreeLevels)-1) / 3); + uint64_t childSubtreeCount = static_cast(pow(4, subtreeLevels)); // 4^N + uint64_t availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + uint64_t childSubtreeAvailabilityByteLength = static_cast(ceil(static_cast(childSubtreeCount) / 8.0)); + builder->nodeAvailabilityByteLengthWithPadding = availabilityByteLength; + builder->childSubtreeAvailabilityByteLengthWithPadding = childSubtreeAvailabilityByteLength; + + builder->datasetCSTileAndChildAvailabilities.insert( + std::pair>>( + CDBDataset::Elevation, + std::map>{} + ) + ); + builder->datasetCSTileAndChildAvailabilities.at(CDBDataset::Elevation).insert( + std::pair>( + "1_1", + std::map{} + ) + ); + std::map &tileAndChildAvailabilities = builder->datasetCSTileAndChildAvailabilities.at(CDBDataset::Elevation).at("1_1"); + int level = 6, x = 47, y = 61; + builder->setParentBitsRecursively(tileAndChildAvailabilities, level, x, y, level, x, y); + + uint64_t childIndex = libmorton::morton2D_64_encode(static_cast(x), static_cast(y)); + uint64_t childByte = childIndex / 8; + uint64_t childBit = childIndex % 8; + int mask = (1 << childBit); + REQUIRE((tileAndChildAvailabilities["0_0_0"].childBuffer[childByte] & mask) >> childBit == 1); + while(level != 0) + { + level -= 1; + if(level == 0) + { + x = 0; + y = 0; + } + else + { + x /= 2; + y /= 2; + } + + uint64_t mortonIndex = libmorton::morton2D_64_encode(static_cast(x), static_cast(y)); + int levelWithinSubtree = level; + const uint64_t nodeCountUpToThisLevel = ((1 << (2 * levelWithinSubtree)) - 1) / 3; + + const uint64_t index = nodeCountUpToThisLevel + mortonIndex; + uint64_t byte = index / 8; + uint64_t bit = index % 8; + mask = (1 << bit); + REQUIRE((tileAndChildAvailabilities["0_0_0"].nodeBuffer[byte] & mask) >> bit == 1); + } + } +} \ No newline at end of file diff --git a/Tests/CDBTilesetTest.cpp b/Tests/CDBTilesetTest.cpp index 930ba0a..ba42e6b 100644 --- a/Tests/CDBTilesetTest.cpp +++ b/Tests/CDBTilesetTest.cpp @@ -140,3 +140,27 @@ TEST_CASE("Test get fit tile", "[CDBTileset]") REQUIRE(fitTile == nullptr); } } + +TEST_CASE("Test get first tile at level", "[CDBTileset]") +{ + // fill the tileset + CDBTileset tileset; + + CDBGeoCell geoCell(32, -118); + CDBTile tile(geoCell, CDBDataset::Elevation, 1, 1, 10, 0, 0); + tileset.insertTile(tile); + + SECTION("Return tile at level 7") + { + int level = 7; + REQUIRE(*tileset.getFirstTileAtLevel(level) == CDBTile(geoCell, CDBDataset::Elevation, 1, 1, level, 0, 0)); + } + + SECTION("Return tile at level 11 with non zero UREF and RREF") + { + int level = 11; + tileset.insertTile(CDBTile(geoCell, CDBDataset::Elevation, 1, 1, level, 6, 2)); + const CDBTile *tileAtLevel = tileset.getFirstTileAtLevel(level); + REQUIRE(*tileAtLevel == CDBTile(geoCell, CDBDataset::Elevation, 1, 1, level, 6, 2)); + } +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 1d2a3d9..0065c40 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2,6 +2,7 @@ project(Tests) add_executable(Tests CombineTilesetsTest.cpp + CDBTilesetBuilderTest.cpp CDBTileTest.cpp CDBTilesetTest.cpp CDBGeoCellTest.cpp @@ -10,7 +11,18 @@ add_executable(Tests CDBGTModelsTest.cpp CDBGSModelsTest.cpp GltfTest.cpp - main.cpp) + main.cpp +) + +set(PRIVATE_THIRD_PARTY_INCLUDE_PATHS + ${LIBMORTON_INCLUDE_DIR} +) +foreach(INCLUDE_DIR ${PRIVATE_THIRD_PARTY_INCLUDE_PATHS}) + target_include_directories(Tests + SYSTEM PUBLIC + ${INCLUDE_DIR} + ) +endforeach() target_link_libraries(Tests PRIVATE diff --git a/Tests/CombineTilesetsTest.cpp b/Tests/CombineTilesetsTest.cpp index ee742c6..6afa539 100644 --- a/Tests/CombineTilesetsTest.cpp +++ b/Tests/CombineTilesetsTest.cpp @@ -1,9 +1,12 @@ +#include "CDB.h" +#include "CDBElevation.h" #include "CDBGeoCell.h" #include "CDBTile.h" #include "CDBTo3DTiles.h" #include "Config.h" #include "catch2/catch.hpp" #include "glm/glm.hpp" +#include "morton.h" #include "nlohmann/json.hpp" #include @@ -407,3 +410,208 @@ TEST_CASE("Test combine multiple sets of tilesets", "[CombineTilesets]") std::filesystem::remove_all(output); } + +TEST_CASE("Test converter for implicit elevation", "[CombineTilesets]") +{ + const uint64_t headerByteLength = 24; + std::filesystem::path input = dataPath / "CombineTilesets"; + CDB cdb(input); + std::filesystem::path output = "CombineTilesets"; + std::filesystem::path elevationTilePath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L02" + / "U2" / "N32W119_D001_S001_T001_L02_U2_R3.tif"; + std::unique_ptr m_impl = std::make_unique(input, output); + std::optional elevation = CDBElevation::createFromFile(elevationTilePath); + SECTION("Test converter errors out of 3D Tiles Next conversion with uninitialized availabilty buffer.") + { + SubtreeAvailability *nullPointer = NULL; + REQUIRE_THROWS_AS(m_impl->addAvailability((*elevation).getTile(), nullPointer, 0, 0, 0), + std::invalid_argument); + } + + // TODO write function for creating buffer given subtree level + int subtreeLevels = 3; + m_impl->subtreeLevels = subtreeLevels; + uint64_t subtreeNodeCount = static_cast((pow(4, subtreeLevels) - 1) / 3); + uint64_t childSubtreeCount = static_cast(pow(4, subtreeLevels)); // 4^N + uint64_t availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + uint64_t childSubtreeAvailabilityByteLength = static_cast( + ceil(static_cast(childSubtreeCount) / 8.0)); + m_impl->nodeAvailabilityByteLengthWithPadding = availabilityByteLength; + m_impl->childSubtreeAvailabilityByteLengthWithPadding = childSubtreeAvailabilityByteLength; + + SubtreeAvailability subtree = m_impl->createSubtreeAvailability(); + + m_impl->addAvailability((*elevation).getTile(), &subtree, 0, 0, 0); + SECTION("Test availability bit is set with correct morton index.") + { + const auto &cdbTile = elevation->getTile(); + uint64_t mortonIndex = libmorton::morton2D_64_encode(static_cast(cdbTile.getRREF()), static_cast(cdbTile.getUREF())); + int levelWithinSubtree = cdbTile.getLevel(); + const uint64_t nodeCountUpToThisLevel = ((1 << (2 * levelWithinSubtree)) - 1) / 3; + + const uint64_t index = nodeCountUpToThisLevel + mortonIndex; + uint64_t byte = index / 8; + uint64_t bit = index % 8; + const uint8_t availability = static_cast(1 << bit); + REQUIRE(subtree.nodeBuffer[byte] == availability); + } + + SECTION("Test available node count is being incremented.") { REQUIRE(subtree.nodeCount == 1); } + + subtreeLevels = 2; + m_impl->subtreeLevels = subtreeLevels; + m_impl->datasetCSTileAndChildAvailabilities.clear(); + subtreeNodeCount = static_cast((pow(4, subtreeLevels) - 1) / 3); + childSubtreeCount = static_cast(pow(4, subtreeLevels)); // 4^N + availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + childSubtreeAvailabilityByteLength = static_cast(ceil(static_cast(childSubtreeCount) / 8.0)); + m_impl->nodeAvailabilityByteLengthWithPadding = availabilityByteLength; + m_impl->childSubtreeAvailabilityByteLengthWithPadding = childSubtreeAvailabilityByteLength; + subtree = m_impl->createSubtreeAvailability(); + std::vector childSubtreeAvailabilityBufferVerified(childSubtreeAvailabilityByteLength); + elevationTilePath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L01" / "U1" + / "N32W119_D001_S001_T001_L01_U1_R1.tif"; + elevation = CDBElevation::createFromFile(elevationTilePath); + m_impl->addAvailability((*elevation).getTile(), &subtree, 0, 0, 0); + + std::filesystem::path elevationChildPath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L02" + / "U2" / "N32W119_D001_S001_T001_L02_U2_R2.tif"; + std::optional elevationChild = CDBElevation::createFromFile(elevationChildPath); + m_impl->addAvailability((*elevationChild).getTile(), &subtree, 2, 2, 2); + elevationChildPath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L02" / "U2" + / "N32W119_D001_S001_T001_L02_U2_R3.tif"; + elevationChild = CDBElevation::createFromFile(elevationChildPath); + m_impl->addAvailability((*elevationChild).getTile(), &subtree, 2, 3, 2); + elevationChildPath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L02" / "U3" + / "N32W119_D001_S001_T001_L02_U3_R2.tif"; + elevationChild = CDBElevation::createFromFile(elevationChildPath); + m_impl->addAvailability((*elevationChild).getTile(), &subtree, 2, 2, 3); + elevationChildPath = input / "Tiles" / "N32" / "W119" / "001_Elevation" / "L02" / "U3" + / "N32W119_D001_S001_T001_L02_U3_R3.tif"; + elevationChild = CDBElevation::createFromFile(elevationChildPath); + m_impl->addAvailability((*elevationChild).getTile(), &subtree, 2, 3, 3); + SECTION("Test child subtree availability bit is set with correct morton index.") + { + const auto &cdbTile = elevation->getTile(); + auto nw = CDBTile::createNorthWestForPositiveLOD(cdbTile); + auto ne = CDBTile::createNorthEastForPositiveLOD(cdbTile); + auto sw = CDBTile::createSouthWestForPositiveLOD(cdbTile); + auto se = CDBTile::createSouthEastForPositiveLOD(cdbTile); + for (auto childTile : {nw, ne, sw, se}) { + uint64_t childMortonIndex = libmorton::morton2D_64_encode(static_cast(childTile.getRREF()), + static_cast(childTile.getUREF())); + const uint64_t childByte = childMortonIndex / 8; + const uint64_t childBit = childMortonIndex % 8; + uint8_t availability = static_cast(1 << childBit); + (&childSubtreeAvailabilityBufferVerified.at(0))[childByte] |= availability; + } + REQUIRE(childSubtreeAvailabilityBufferVerified + == m_impl->datasetCSTileAndChildAvailabilities.at(CDBDataset::Elevation) + .at("1_1") + .at("0_0_0") + .childBuffer); + } + + SECTION("Test availability buffer correct length for subtree level and verify subtree json.") + { + subtreeLevels = 4; + Converter converter(input, output); + converter.setSubtreeLevels(subtreeLevels); + converter.setUse3dTilesNext(true); + converter.convert(); + + std::filesystem::path subtreeBinary = output / "Tiles" / "N32" / "W119" / "Elevation" / "1_1" + / "subtrees" / "0_0_0.subtree"; + REQUIRE(std::filesystem::exists(subtreeBinary)); + + subtreeNodeCount = static_cast((pow(4, subtreeLevels) - 1) / 3); + availabilityByteLength = static_cast(ceil(static_cast(subtreeNodeCount) / 8.0)); + const uint64_t nodeAvailabilityByteLengthWithPadding = alignTo8(availabilityByteLength); + + // buffer length is header + json + node availability buffer + child subtree availability (constant in this case, so no buffer) + std::filesystem::path binaryBufferPath = output / "Tiles" / "N32" / "W119" / "Elevation" / "1_1" + / "availability" / "0_0_0.bin"; + REQUIRE(std::filesystem::exists(binaryBufferPath)); + std::ifstream availabilityInputStream(binaryBufferPath, std::ios_base::binary); + std::vector availabilityBuffer(std::istreambuf_iterator(availabilityInputStream), + {}); + + std::ifstream subtreeInputStream(subtreeBinary, std::ios_base::binary); + std::vector subtreeBuffer(std::istreambuf_iterator(subtreeInputStream), {}); + + uint64_t jsonStringByteLength = *(uint64_t *) &subtreeBuffer[8]; // 64-bit int from 8 8-bit ints + uint32_t binaryBufferByteLength = static_cast(availabilityBuffer.size()); + REQUIRE(binaryBufferByteLength == nodeAvailabilityByteLengthWithPadding); + + std::vector::iterator jsonBeginning = subtreeBuffer.begin() + headerByteLength; + std::string jsonString(jsonBeginning, jsonBeginning + static_cast(jsonStringByteLength)); + nlohmann::json subtreeJson = nlohmann::json::parse(jsonString); + std::ifstream fs(input / "VerifiedSubtree.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(fs); + REQUIRE(subtreeJson == verifiedJson); + } + + SECTION("Test that subtree JSON has no buffer object when availabilities are both constant.") + { + input = dataPath / "CombineTilesetsSmallElevation"; + output = "CombineTilesetsSmallElevation"; + subtreeLevels = 2; + Converter converter(input, output); + converter.setSubtreeLevels(subtreeLevels); + converter.setUse3dTilesNext(true); + converter.convert(); + + std::filesystem::path subtreeBinary = output / "Tiles" / "N32" / "W119" / "Elevation" / "1_1" + / "subtrees" / "0_0_0.subtree"; + REQUIRE(std::filesystem::exists(subtreeBinary)); + + std::ifstream inputStream(subtreeBinary, std::ios_base::binary); + std::vector buffer(std::istreambuf_iterator(inputStream), {}); + uint64_t jsonStringByteLength = *(uint64_t *) &buffer[8]; + + std::vector::iterator jsonBeginning = buffer.begin() + headerByteLength; + std::string jsonString(jsonBeginning, jsonBeginning + static_cast(jsonStringByteLength)); + nlohmann::json subtreeJson = nlohmann::json::parse(jsonString); + + REQUIRE(subtreeJson.find("buffers") == subtreeJson.end()); + REQUIRE(subtreeJson.find("bufferViews") == subtreeJson.end()); + } + + SECTION("Verify geocell tileset json.") + { + subtreeLevels = 4; + Converter converter(input, output); + converter.setSubtreeLevels(subtreeLevels); + converter.setUse3dTilesNext(true); + converter.convert(); + + std::filesystem::path geoCellJson = output / "Tiles" / "N32" / "W119" / "Elevation" / "1_1" + / "N32W119_D001_S001_T001.json"; + REQUIRE(std::filesystem::exists(geoCellJson)); + std::ifstream fs(geoCellJson); + nlohmann::json tilesetJson = nlohmann::json::parse(fs); + nlohmann::json child = tilesetJson["root"]; + + // Get down to the last explicitly defined tile + while (child.find("children") != child.end()) { + child = child["children"][0]; + } + + nlohmann::json implicitTiling = child["extensions"]["3DTILES_implicit_tiling"]; + REQUIRE(implicitTiling["maximumLevel"] == 2); + REQUIRE(implicitTiling["subdivisionScheme"] == "QUADTREE"); + REQUIRE(implicitTiling["subtreeLevels"] == subtreeLevels); + REQUIRE(implicitTiling["subtrees"]["uri"] == "subtrees/{level}_{x}_{y}.subtree"); + + // Make sure extensions are in extensionsUsed and extensionsRequired + nlohmann::json extensionsUsed = tilesetJson["extensionsUsed"]; + REQUIRE(std::find(extensionsUsed.begin(), extensionsUsed.end(), "3DTILES_implicit_tiling") + != extensionsUsed.end()); + + nlohmann::json extensionsRequired = tilesetJson["extensionsRequired"]; + REQUIRE(std::find(extensionsRequired.begin(), extensionsRequired.end(), "3DTILES_implicit_tiling") + != extensionsRequired.end()); + } + + std::filesystem::remove_all(output); +} diff --git a/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif differ diff --git a/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesets/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif differ diff --git a/Tests/Data/CombineTilesets/VerifiedSubtree.json b/Tests/Data/CombineTilesets/VerifiedSubtree.json new file mode 100644 index 0000000..c4f17b1 --- /dev/null +++ b/Tests/Data/CombineTilesets/VerifiedSubtree.json @@ -0,0 +1 @@ +{"bufferViews":[{"buffer":0,"byteLength":11,"byteOffset":0},{"buffer":1,"byteLength":11,"byteOffset":0}],"buffers":[{"byteLength":16},{"byteLength":16,"uri":"../availability/0_0_0.bin"}],"childSubtreeAvailability":{"constant":0},"contentAvailability":{"bufferView":1},"tileAvailability":{"bufferView":0}} diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D500_S001_T001_AL015_000_coronado_bridge.flt b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D500_S001_T001_AL015_000_coronado_bridge.flt new file mode 100644 index 0000000..3393b62 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D500_S001_T001_AL015_000_coronado_bridge.flt differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D503_S001_T001_AL015_000_coronado_bridge.xml b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D503_S001_T001_AL015_000_coronado_bridge.xml new file mode 100644 index 0000000..7c01ea1 --- /dev/null +++ b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/A_Culture/L_Misc_Feature/015_Building/D503_S001_T001_AL015_000_coronado_bridge.xml @@ -0,0 +1,137 @@ + + + + coronado_bridge + + + + + AL015 + + + 0 + + + + + + + 1 + + + 1 + + + 0 + 8 + + + 19.1299 + + + + 1515.82 + 1536.17 + + + -194.026 + 1.17549e-038 + + + + + + 1 + + + 1 + + + 0 + 8 + + + 12.3775 + + + + 1550.61 + 1583.26 + + + -194.007 + 1.17549e-038 + + + + + + 1 + + + 1 + + + 0 + 8 + + + 27.4114 + + + + 1515.82 + 1536.17 + + + -184.991 + 1.17549e-038 + + + + + + + + GENBUILDING + + + + + BM_CONCRETE + + + 60 + + + + + BM_ASPHALT + + + 20 + + + + + BM_SHINGLE + + + 10 + + + + + BM_WOOD-DECIDUOUS + + + 10 + + + + 1 + + + + + \ No newline at end of file diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_012_coniferous_tree01.flt b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_012_coniferous_tree01.flt new file mode 100644 index 0000000..ffd1833 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_012_coniferous_tree01.flt differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_017_palm_tree01.flt b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_017_palm_tree01.flt new file mode 100644 index 0000000..190248e Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D500_S001_T001_EC030_017_palm_tree01.flt differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_012_coniferous_tree01.xml b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_012_coniferous_tree01.xml new file mode 100644 index 0000000..8943628 --- /dev/null +++ b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_012_coniferous_tree01.xml @@ -0,0 +1,63 @@ + + + + coniferous_tree01 + + + + + EC030 + + + 12 + + + + + + + 1 + + + 1 + + + 0 + 9 + + + 25.1751 + + + + 0.00325301 + 0.991602 + + + 0.016191 + 0.076119 + + + + + + + + TREE + + + + + BM_WOOD-DECIDUOUS + + + 100 + + + + 1 + + + + + \ No newline at end of file diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_017_palm_tree01.xml b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_017_palm_tree01.xml new file mode 100644 index 0000000..87bd239 --- /dev/null +++ b/Tests/Data/CombineTilesetsSmallElevation/GTModel/500_GTModelGeometry/E_Vegetation/C_Woodland/030_Trees/D503_S001_T001_EC030_017_palm_tree01.xml @@ -0,0 +1,63 @@ + + + + palm_tree01 + + + + + EC030 + + + 17 + + + + + + + 1 + + + 1 + + + 0 + 9 + + + 57.1181 + + + + -2.4444e-012 + 1 + + + 0.0613848 + 0.0888282 + + + + + + + + TREE + + + + + BM_WOOD-DECIDUOUS + + + 100 + + + + 1 + + + + + \ No newline at end of file diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W00_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W00_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..f3a50ae Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W00_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W01_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W01_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..b4f3870 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W01_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W02_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W02_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..57099c8 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W02_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W03_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W03_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..48e349a Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W03_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W04_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W04_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..946aeab Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W04_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W05_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W05_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..2bf969b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W05_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W06_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W06_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..51aeb12 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W06_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W07_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W07_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..d4d28e7 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W07_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W08_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W08_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..1b49807 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W08_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W09_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W09_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..5aefddb Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W09_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W10_ComericaBankSanDiego.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W10_ComericaBankSanDiego.rgb new file mode 100644 index 0000000..b2a15b4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/ComericaBankSanDiego/D501_S001_T001_W10_ComericaBankSanDiego.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W00_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W00_coronado_bridge.rgb new file mode 100644 index 0000000..49038fa Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W00_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W01_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W01_coronado_bridge.rgb new file mode 100644 index 0000000..7bcde0d Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W01_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W02_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W02_coronado_bridge.rgb new file mode 100644 index 0000000..b38724b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W02_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W03_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W03_coronado_bridge.rgb new file mode 100644 index 0000000..e3cc6bd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W03_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W04_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W04_coronado_bridge.rgb new file mode 100644 index 0000000..bd54141 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W04_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W05_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W05_coronado_bridge.rgb new file mode 100644 index 0000000..af7fd2a Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W05_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W06_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W06_coronado_bridge.rgb new file mode 100644 index 0000000..04cf810 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W06_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W07_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W07_coronado_bridge.rgb new file mode 100644 index 0000000..ab7f2d8 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W07_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W08_coronado_bridge.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W08_coronado_bridge.rgb new file mode 100644 index 0000000..e8d99e7 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridge/D501_S001_T001_W08_coronado_bridge.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W00_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W00_coronado_bridgeroad.rgb new file mode 100644 index 0000000..7f228c5 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W00_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W01_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W01_coronado_bridgeroad.rgb new file mode 100644 index 0000000..9ff5fa3 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W01_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W02_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W02_coronado_bridgeroad.rgb new file mode 100644 index 0000000..ad5b2dc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W02_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W03_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W03_coronado_bridgeroad.rgb new file mode 100644 index 0000000..9c2ab06 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W03_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W04_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W04_coronado_bridgeroad.rgb new file mode 100644 index 0000000..847467b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W04_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W05_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W05_coronado_bridgeroad.rgb new file mode 100644 index 0000000..26882db Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W05_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W06_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W06_coronado_bridgeroad.rgb new file mode 100644 index 0000000..1f7011d Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W06_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W07_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W07_coronado_bridgeroad.rgb new file mode 100644 index 0000000..0d65211 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W07_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W08_coronado_bridgeroad.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W08_coronado_bridgeroad.rgb new file mode 100644 index 0000000..c84fc99 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeroad/D501_S001_T001_W08_coronado_bridgeroad.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W00_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W00_coronado_bridgeside.rgb new file mode 100644 index 0000000..a037912 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W00_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W01_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W01_coronado_bridgeside.rgb new file mode 100644 index 0000000..e3567bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W01_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W02_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W02_coronado_bridgeside.rgb new file mode 100644 index 0000000..9c9d5c2 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W02_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W03_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W03_coronado_bridgeside.rgb new file mode 100644 index 0000000..4ef9e4c Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W03_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W04_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W04_coronado_bridgeside.rgb new file mode 100644 index 0000000..8961a82 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W04_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W05_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W05_coronado_bridgeside.rgb new file mode 100644 index 0000000..e3719c3 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W05_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W06_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W06_coronado_bridgeside.rgb new file mode 100644 index 0000000..56338ec Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W06_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W07_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W07_coronado_bridgeside.rgb new file mode 100644 index 0000000..c1aa25c Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W07_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W08_coronado_bridgeside.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W08_coronado_bridgeside.rgb new file mode 100644 index 0000000..10d64f5 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/C/O/coronado_bridgeside/D501_S001_T001_W08_coronado_bridgeside.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W00_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W00_tree.rgb new file mode 100644 index 0000000..dbfe882 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W00_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W01_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W01_tree.rgb new file mode 100644 index 0000000..773ef17 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W01_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W02_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W02_tree.rgb new file mode 100644 index 0000000..eb59b2e Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W02_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W03_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W03_tree.rgb new file mode 100644 index 0000000..a4ba254 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W03_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W04_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W04_tree.rgb new file mode 100644 index 0000000..1197dd9 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W04_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W05_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W05_tree.rgb new file mode 100644 index 0000000..14db17a Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W05_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W06_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W06_tree.rgb new file mode 100644 index 0000000..a97c70a Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W06_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W07_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W07_tree.rgb new file mode 100644 index 0000000..8393fc1 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W07_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W08_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W08_tree.rgb new file mode 100644 index 0000000..ae9096b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W08_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb new file mode 100644 index 0000000..e5ea37b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb.attr b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb.attr new file mode 100644 index 0000000..9a427fc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/GTModel/501_GTModelTexture/T/R/tree/D501_S001_T001_W09_tree.rgb.attr differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.dbf new file mode 100644 index 0000000..3967367 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shp new file mode 100644 index 0000000..68e6c9f Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shx new file mode 100644 index 0000000..9859a84 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T001_L00_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.dbf new file mode 100644 index 0000000..3be527e Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S001_T002_L00_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.dbf new file mode 100644 index 0000000..ede9601 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shp new file mode 100644 index 0000000..96ce9d5 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shx new file mode 100644 index 0000000..fed0ef6 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T001_L00_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.dbf new file mode 100644 index 0000000..ece5212 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L00/U0/N32W118_D101_S002_T002_L00_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T001_L01_U1_R1.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/L01/U1/N32W118_D101_S002_T002_L01_U1_R1.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC01_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC02_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC03_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC04_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC05_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC06_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC07_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC08_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC09_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T001_LC10_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC01_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC02_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC03_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC04_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC05_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC06_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC07_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC08_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC09_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S001_T002_LC10_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC01_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC02_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC03_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC04_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC05_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC06_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC07_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC08_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC09_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shp new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shx new file mode 100644 index 0000000..04604cd Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T001_LC10_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC01_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC02_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC03_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC04_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC05_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC06_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC07_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC08_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC09_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.dbf new file mode 100644 index 0000000..262e8bc Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/101_GTFeature/LC/U0/N32W118_D101_S002_T002_LC10_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.dbf new file mode 100644 index 0000000..cf18303 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shp new file mode 100644 index 0000000..7e5d410 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shp differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shx new file mode 100644 index 0000000..239e66b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T003_LC05_U0_R0.shx differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.dbf b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.dbf new file mode 100644 index 0000000..aed132e Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.dbf differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.dbt b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.dbt new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.shp b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.shp new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.shx b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W118/201_RoadNetwork/LC/U0/N32W118_D201_S002_T004_LC05_U0_R0.shx new file mode 100644 index 0000000..e69de29 diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L00/U0/N32W119_D001_S001_T001_L00_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L00/U0/N32W119_D001_S001_T001_L00_U0_R0.tif new file mode 100644 index 0000000..cabc738 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L00/U0/N32W119_D001_S001_T001_L00_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U0/N32W119_D001_S001_T001_L01_U0_R1.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R1.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R1.tif new file mode 100644 index 0000000..44621a4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/L01/U1/N32W119_D001_S001_T001_L01_U1_R1.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC01_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC01_U0_R0.tif new file mode 100644 index 0000000..e29a385 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC01_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC02_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC02_U0_R0.tif new file mode 100644 index 0000000..9ba3b29 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC02_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC03_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC03_U0_R0.tif new file mode 100644 index 0000000..b631a37 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC03_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC04_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC04_U0_R0.tif new file mode 100644 index 0000000..e7d7fc0 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC04_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC05_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC05_U0_R0.tif new file mode 100644 index 0000000..98ca749 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC05_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC06_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC06_U0_R0.tif new file mode 100644 index 0000000..1fccfd4 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC06_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC07_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC07_U0_R0.tif new file mode 100644 index 0000000..2b15476 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC07_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC08_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC08_U0_R0.tif new file mode 100644 index 0000000..d387d42 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC08_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC09_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC09_U0_R0.tif new file mode 100644 index 0000000..0617d2b Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC09_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC10_U0_R0.tif b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC10_U0_R0.tif new file mode 100644 index 0000000..89d13c9 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/001_Elevation/LC/U0/N32W119_D001_S001_T001_LC10_U0_R0.tif differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L00/U0/N32W119_D004_S001_T001_L00_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L00/U0/N32W119_D004_S001_T001_L00_U0_R0.jp2 new file mode 100644 index 0000000..170a922 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L00/U0/N32W119_D004_S001_T001_L00_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U0/N32W119_D004_S001_T001_L01_U0_R1.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U0/N32W119_D004_S001_T001_L01_U0_R1.jp2 new file mode 100644 index 0000000..6cdf773 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U0/N32W119_D004_S001_T001_L01_U0_R1.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U1/N32W119_D004_S001_T001_L01_U1_R1.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U1/N32W119_D004_S001_T001_L01_U1_R1.jp2 new file mode 100644 index 0000000..5c4589e Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/L01/U1/N32W119_D004_S001_T001_L01_U1_R1.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC01_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC01_U0_R0.jp2 new file mode 100644 index 0000000..e153f03 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC01_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC02_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC02_U0_R0.jp2 new file mode 100644 index 0000000..bc441f0 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC02_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC03_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC03_U0_R0.jp2 new file mode 100644 index 0000000..619c722 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC03_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC04_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC04_U0_R0.jp2 new file mode 100644 index 0000000..f628727 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC04_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC05_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC05_U0_R0.jp2 new file mode 100644 index 0000000..6a5e272 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC05_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC06_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC06_U0_R0.jp2 new file mode 100644 index 0000000..0695743 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC06_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC07_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC07_U0_R0.jp2 new file mode 100644 index 0000000..a0a7ebf Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC07_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC08_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC08_U0_R0.jp2 new file mode 100644 index 0000000..fe4c140 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC08_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC09_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC09_U0_R0.jp2 new file mode 100644 index 0000000..855f664 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC09_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC10_U0_R0.jp2 b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC10_U0_R0.jp2 new file mode 100644 index 0000000..a24d030 Binary files /dev/null and b/Tests/Data/CombineTilesetsSmallElevation/Tiles/N32/W119/004_Imagery/LC/U0/N32W119_D004_S001_T001_LC10_U0_R0.jp2 differ diff --git a/Tests/Data/CombineTilesetsSmallElevation/VerifiedSubtree.json b/Tests/Data/CombineTilesetsSmallElevation/VerifiedSubtree.json new file mode 100644 index 0000000..0fb3301 --- /dev/null +++ b/Tests/Data/CombineTilesetsSmallElevation/VerifiedSubtree.json @@ -0,0 +1 @@ +{"bufferViews":[{"buffer":0,"byteLength":11,"byteOffset":0}],"buffers":[{"byteLength":11}],"childSubtreeAvailability":{"constant":0},"contentAvailability":{"bufferView":0},"tileAvailability":{"bufferView":0}} diff --git a/Tests/Data/ElevationMoreLODNegativeImagery/VerifiedTileset.json b/Tests/Data/ElevationMoreLODNegativeImagery/VerifiedTileset.json index a47bb67..6ab0e65 100644 --- a/Tests/Data/ElevationMoreLODNegativeImagery/VerifiedTileset.json +++ b/Tests/Data/ElevationMoreLODNegativeImagery/VerifiedTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.050761871093337,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.059488517353309,0.5672320068981571,-2.050761871093337,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L00_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC01_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC02_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC03_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC04_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC05_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC06_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC07_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC08_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC09_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0507618710933375,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0594885173533086,0.5672320068981571,-2.0507618710933375,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.0420352248333655,0.5759586531581288,-1.5761057138442993,475.4639892578125]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L0_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC1_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC2_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC3_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC4_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC5_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC6_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC7_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC8_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC9_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/Data/ElevationMoreLODPositiveImagery/VerifiedTileset.json b/Tests/Data/ElevationMoreLODPositiveImagery/VerifiedTileset.json index b45f03f..6d9b2cc 100644 --- a/Tests/Data/ElevationMoreLODPositiveImagery/VerifiedTileset.json +++ b/Tests/Data/ElevationMoreLODPositiveImagery/VerifiedTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.050761871093337,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.059488517353309,0.5672320068981571,-2.050761871093337,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0463985479633515,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U2_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5672320068981571,-2.0420352248333655,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U2_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5715953300281429,-2.0463985479633515,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U3_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5715953300281429,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U3_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R1.b3dm"},"geometricError":146.484375}],"content":{"uri":"N32W118_D001_S001_T001_L00_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC01_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC02_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC03_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC04_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC05_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC06_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC07_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC08_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC09_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0507618710933375,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0594885173533086,0.5672320068981571,-2.0507618710933375,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.0420352248333655,0.5759586531581288,-2.086463689804077,481.7667236328125]},"children":[{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.046398547963351,0.5715953300281429,0.0,119.05301666259766]},"content":{"uri":"N32W118_D001_S001_T001_L2_U2_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5672320068981571,-2.0420352248333655,0.5715953300281429,-2.086463689804077,261.4966125488281]},"content":{"uri":"N32W118_D001_S001_T001_L2_U2_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5715953300281429,-2.046398547963351,0.5759586531581288,-0.012611250393092632,249.0120086669922]},"content":{"uri":"N32W118_D001_S001_T001_L2_U3_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5715953300281429,-2.0420352248333655,0.5759586531581288,-0.45716002583503723,481.7667236328125]},"content":{"uri":"N32W118_D001_S001_T001_L2_U3_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R1.b3dm"},"geometricError":146.484375}],"content":{"uri":"N32W118_D001_S001_T001_L0_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC1_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC2_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC3_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC4_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC5_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC6_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC7_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC8_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC9_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Metadata/Materials.xml b/Tests/Data/ElevationWithRMTextureRMDescriptor/Metadata/Materials.xml new file mode 100755 index 0000000..8296f29 --- /dev/null +++ b/Tests/Data/ElevationWithRMTextureRMDescriptor/Metadata/Materials.xml @@ -0,0 +1,663 @@ + + + + 1 + 1 + + CDB Specification v2.0 + + BM_ASH + Ash (generic) + + + + BM_ASH-VOLCANIC + Volcanic Ash + + + + BM_ASPHALT + Asphalt + + + + BM_BRICK + Brick + + + + BM_CEMENT + Cement + + + + BM_CINDERS + Cinders + + + + BM_CIRRIPEDIA + Cirripedia + + + + BM_CLAY + Clay (dry) + + + + BM_COAL + Coal + + + + BM_COKE + Coke (Fossil coal charred) + + + + BM_CONCRETE + Concrete + + + + BM_CONCRETE-REINFORCED + Reinforced Concrete + + + + BM_CONCRETE-PRESTRESSED + Prestressed Concrete + + + + BM_CORAL + Coral + + + + BM_DIATOMS + Diatoms + + + + BM_DOLOMITE + Dolomite (magnesium carbonate of lime) + + + + BM_DRYWALL + Drywall panel (aka plasterboard) + + + + BM_EARTHEN + Baked clay + + + + BM_FABRIC + Cloth, material + + + + BM_FIBERBOARD + Fiberboard panel + + + + BM_FIBREGLASS + Fiber glass + + + + BM_FLYSH + Flysh (sedimentary) + + + + BM_FOLIAGE-DECIDUOUS + Decidious leaves + + + + BM_FOLIAGE-NEEDLES + Needle leaves + + + + BM_FORAMINIFERA + Foraminifera + + + + BM_FUCUS + Fucus (seaweed) + + + + BM_FUR + Fur + + + + BM_GASOLINE + Gasoline + + + + BM_GLASS + Glass + + + + BM_GLOBIGERINA + Globigerina + + + + BM_GOLD + Gold + + + + BM_GRANITE + Granite + + + + BM_THATCH + Grass Thatch + + + + BM_GROUND-SHELLS + Ground shells + + + + BM_ICE-PERMANENT + Permanent ice + + + + BM_LAND-CEREALS + Grain field + + + + BM_LAND-CULTIVATED + Cultivated land (vegetables) + + + + BM_LAND-GRASS + Grassy land + + + + BM_LAND-HIGH_MEADOW + High vegetation + + + + BM_LAND-LOW_MEADOW + Low vegetation + + + + BM_LAND-MOOR + Temperate climate vegetation + + + + BM_LAND-PLOUGHED + Ploughed land, free of vegetation + + + + BM_LOESS + Loess + + + + BM_LUMBER + Plank of wood + + + + BM_MACADAM + Macadam (roadway constructed by compacting into a solid mass of broken stone using cement or asphalt as binder) + + + + BM_MADREPORES + Madrepores + + + + BM_MARL + Calcium carbonate deposit + + + + BM_METAL + Generic metal + + + + BM_METAL-ALUMINIUM + Aluminium + + + + BM_METAL-COPPER + Copper + + + + BM_METAL-LEAD + Lead + + + + BM_METAL-IRON + Raw iron + + + + BM_METAL-MANGANESE + Manganese + + + + BM_METAL-MATTES + Mattes (metallic product of the smelting of ores) + + + + BM_METAL-SLAG + Slag (recrement of metal) + + + + BM_METAL-SCORIA + Scoria + + + + BM_METAL-SILVER + Silver + + + + BM_METAL-STEEL + Steel + + + + BM_METAL-ZINC + Zinc + + + + BM_METAL-URANIUM + Uranium + + + + BM_MOISTURE + Embedded water in porous materials + + + + BM_MUSSELS + Mussels + + + + BM_OIL + Oil + + + + BM_OYSTERS + Oysters + + + + BM_PAPER + Paper + + + + BM_PAINT + Generic paint + + + + BM_PAINT-ASPHALT + Paint applied to asphalt (highways, runway markings) + + + + BM_PAINT-CONCRETE + Paint applied to concrete (typically runway markings) + + + + BM_PAINT-LATEX + Latex paint + + + + BM_PAINT-OIL + Oil paint + + + + BM_PLASTIC + Rigid plastic + + + + BM_PLYWOOD + Plywood panel + + + + BM_POLYZOA + Polyzoa + + + + BM_PTEROPODS + Pteropods + + + + BM_RAIDOLARIA + Radiolaria + + + + BM_RADIOACTIVE + Radioactive material + + + + BM_ROCK + Generic rock + + + + BM_ROCK-BASALT + Basalt rock (cliffs) + + + + BM_ROCK-BEDROCK + Bedrock + + + + BM_BOULDERS + Boulders + + + + BM_ROCK-CALCAREOUS + Limestone rock + + + + BM_ROCK-CHALK + Chalk rock (cliffs) + + + + BM_ROCK-EVAPORITES + Evaporites (sedimentary gypsum or rock salt) + + + + BM_ROCK-GRAVEL + Gravel rock + + + + BM_ROCK-LAVA + Lava rock + + + + BM_ROCK-MARBLE + Marble rock + + + + BM_ROCK-PEBBLES + Pebbles + + + + BM_ROCK-PORPHYRY + Porphyry rock + + + + BM_ROCK-PUMICE + Pumice rock + + + + BM_ROCK-RUBBLE + Rubble + + + + BM_ROCK-STONE + Stone (walls) + + + + BM_ROCK-QUARTZ + Quartz rock + + + + BM_ROCK-SANDSTONE + Sandstone rock + + + + BM_ROCK-SCHIST + Schist rock + + + + BM_ROCK-SLATE + Slate rock + + + + BM_ROCK-TAILINGS + Mine tailings + + + + BM_ROCK-TUFA + Calcium carbonate limestone + + + + BM_ROCK-VOLCANIC + Volcanic rock + + + + BM_RUBBER + Rubber (tires, coatings) + + + + BM_SALT + Salt + + + + BM_SAND + Sand + + + + BM_SEA-TANGLE + Sea Tangle + + + + BM_SEAWEED + Seaweed + + + + BM_SEWAGE + Sewage + + + + BM_SHELLS + Shells + + + + BM_SKIN + Skin (of living animals or humans) + + + + BM_SHINGLE + Shingle (tar) + + + + BM_SNOW-PERMANENT + Permanent snow + + + + BM_SOIL + Generic soil + + + + BM_SOIL-BRUSH + Brush ground + + + + BM_SOIL-CLAY + Clayey ground + + + + BM_SOIL-MARL + Marl (calcarious earth) + + + + BM_SOIL-OOZE + Ooze (mud, silt, sludge or slime that flows) + + + + BM_SOIL-PEAT + Peat moss + + + + BM_SOIL-SILT + Silt + + + + BM_SPICULES + Spicules + + + + BM_SPONGE + Sponge + + + + BM_STONE-BRICK + Brick stone (masonry) + + + + BM_STONE-COBBLE + Cobble stone + + + + BM_STRAW + Straw + + + + BM_SUGAR + Sugar + + + + BM_TAR + Tar + + + + BM_TRAVERTINE + Travertine + + + + BM_VEGETATION-BRUSH + Brush or undergrowth + + + + BM_VEGETATION-DEBRIS + Natural detritus, dead leaves, small branches + + + + BM_WATER + Generic water + + + + BM_WATER-FRESH-FLOWING + Fresh flowing water (e.g. rivers) + + + + BM_WATER-FRESH-STANDING + Fresh standing water (e.g. lacs + + + + BM_WATER-OCEAN + Salt water (e.g. oceans, seas) + + + + BM_WOOD-DECIDUOUS + Decidious trees + + + + BM_WOOD-CONIFER + Coniferous trees + + + diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L00/U0/N12E044_D001_S001_T001_L00_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L00/U0/N12E044_D001_S001_T001_L00_U0_R0.tif new file mode 100755 index 0000000..1db70cf Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L00/U0/N12E044_D001_S001_T001_L00_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L01/U1/N12E044_D001_S001_T001_L01_U1_R1.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L01/U1/N12E044_D001_S001_T001_L01_U1_R1.tif new file mode 100755 index 0000000..c569dcd Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/L01/U1/N12E044_D001_S001_T001_L01_U1_R1.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC01_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC01_U0_R0.tif new file mode 100755 index 0000000..3f30bb1 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC01_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC02_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC02_U0_R0.tif new file mode 100755 index 0000000..93e42cc Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/001_Elevation/LC/U0/N12E044_D001_S001_T001_LC02_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L00/U0/N12E044_D004_S001_T001_L00_U0_R0.jp2 b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L00/U0/N12E044_D004_S001_T001_L00_U0_R0.jp2 new file mode 100755 index 0000000..6807dae Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L00/U0/N12E044_D004_S001_T001_L00_U0_R0.jp2 differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L01/U1/N12E044_D004_S001_T001_L01_U1_R1.jp2 b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L01/U1/N12E044_D004_S001_T001_L01_U1_R1.jp2 new file mode 100755 index 0000000..eaa5343 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/L01/U1/N12E044_D004_S001_T001_L01_U1_R1.jp2 differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC01_U0_R0.jp2 b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC01_U0_R0.jp2 new file mode 100755 index 0000000..93df26f Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC01_U0_R0.jp2 differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC02_U0_R0.jp2 b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC02_U0_R0.jp2 new file mode 100755 index 0000000..c33aa49 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/004_Imagery/LC/U0/N12E044_D004_S001_T001_LC02_U0_R0.jp2 differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L00/U0/N12E044_D005_S001_T001_L00_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L00/U0/N12E044_D005_S001_T001_L00_U0_R0.tif new file mode 100755 index 0000000..29bca01 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L00/U0/N12E044_D005_S001_T001_L00_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L01/U1/N12E044_D005_S001_T001_L01_U1_R1.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L01/U1/N12E044_D005_S001_T001_L01_U1_R1.tif new file mode 100755 index 0000000..1876614 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/L01/U1/N12E044_D005_S001_T001_L01_U1_R1.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC01_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC01_U0_R0.tif new file mode 100755 index 0000000..082115f Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC01_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC02_U0_R0.tif b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC02_U0_R0.tif new file mode 100755 index 0000000..aaa2a94 Binary files /dev/null and b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/005_RMTexture/LC/U0/N12E044_D005_S001_T001_LC02_U0_R0.tif differ diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L00/U0/N12E044_D006_S001_T001_L00_U0_R0.xml b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L00/U0/N12E044_D006_S001_T001_L00_U0_R0.xml new file mode 100755 index 0000000..d328e6a --- /dev/null +++ b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L00/U0/N12E044_D006_S001_T001_L00_U0_R0.xml @@ -0,0 +1 @@ +OCEANBM_WATER-OCEAN100DRYGROUNDBM_SOIL95BM_MOISTURE5CALCAREOUSBM_ROCK-CALCAREOUS100SANDBM_SAND100CITYBM_CONCRETE40BM_FOLIAGE-DECIDUOUS20BM_ASPHALT20BM_WOOD-DECIDUOUS10BM_SHINGLE10ASPHALTROADBM_ASPHALT100FORESTBM_FOLIAGE-DECIDUOUS85BM_WOOD-DECIDUOUS15WETGROUNDBM_SOIL-OOZE100 diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L01/U1/N12E044_D006_S001_T001_L01_U1_R1.xml b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L01/U1/N12E044_D006_S001_T001_L01_U1_R1.xml new file mode 100755 index 0000000..1916f38 --- /dev/null +++ b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/L01/U1/N12E044_D006_S001_T001_L01_U1_R1.xml @@ -0,0 +1 @@ +OCEANBM_WATER-OCEAN100DRYGROUNDBM_SOIL95BM_MOISTURE5CALCAREOUSBM_ROCK-CALCAREOUS100SANDBM_SAND100FORESTBM_FOLIAGE-DECIDUOUS85BM_WOOD-DECIDUOUS15CITYBM_CONCRETE40BM_FOLIAGE-DECIDUOUS20BM_ASPHALT20BM_WOOD-DECIDUOUS10BM_SHINGLE10ASPHALTROADBM_ASPHALT100GRASSBM_LAND-GRASS100WETGROUNDBM_SOIL-OOZE100 diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC01_U0_R0.xml b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC01_U0_R0.xml new file mode 100755 index 0000000..6f5fa34 --- /dev/null +++ b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC01_U0_R0.xml @@ -0,0 +1 @@ +OCEANBM_WATER-OCEAN100DRYGROUNDBM_SOIL95BM_MOISTURE5CALCAREOUSBM_ROCK-CALCAREOUS100CITYBM_CONCRETE40BM_FOLIAGE-DECIDUOUS20BM_ASPHALT20BM_WOOD-DECIDUOUS10BM_SHINGLE10ASPHALTROADBM_ASPHALT100 diff --git a/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC02_U0_R0.xml b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC02_U0_R0.xml new file mode 100755 index 0000000..6f5fa34 --- /dev/null +++ b/Tests/Data/ElevationWithRMTextureRMDescriptor/Tiles/N12/E044/006_RMDescriptor/LC/U0/N12E044_D006_S001_T001_LC02_U0_R0.xml @@ -0,0 +1 @@ +OCEANBM_WATER-OCEAN100DRYGROUNDBM_SOIL95BM_MOISTURE5CALCAREOUSBM_ROCK-CALCAREOUS100CITYBM_CONCRETE40BM_FOLIAGE-DECIDUOUS20BM_ASPHALT20BM_WOOD-DECIDUOUS10BM_SHINGLE10ASPHALTROADBM_ASPHALT100 diff --git a/Tests/Data/GSModelsWithGTModelTexture/VerifiedTileset.json b/Tests/Data/GSModelsWithGTModelTexture/VerifiedTileset.json index 232bf6e..629dfba 100644 --- a/Tests/Data/GSModelsWithGTModelTexture/VerifiedTileset.json +++ b/Tests/Data/GSModelsWithGTModelTexture/VerifiedTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D300_S001_T001_L01_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D300_S001_T001_L00_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D300_S001_T001_LC01_U0_R0.b3dm"},"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"ADD"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D300_S001_T001_L1_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D300_S001_T001_L0_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D300_S001_T001_LC1_U0_R0.b3dm"},"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"ADD"}} diff --git a/Tests/Data/GTModels/VerifiedBridgeTileset.json b/Tests/Data/GTModels/VerifiedBridgeTileset.json index a30612c..798d6d4 100644 --- a/Tests/Data/GTModels/VerifiedBridgeTileset.json +++ b/Tests/Data/GTModels/VerifiedBridgeTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D101_S001_T001_L00_U0_R0.cmpt"},"geometricError":0.0}],"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D101_S001_T001_L0_U0_R0.cmpt"},"geometricError":0.0}],"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/Data/GTModels/VerifiedTreeTileset.json b/Tests/Data/GTModels/VerifiedTreeTileset.json index cbc6860..8217ca7 100644 --- a/Tests/Data/GTModels/VerifiedTreeTileset.json +++ b/Tests/Data/GTModels/VerifiedTreeTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D101_S002_T001_L00_U0_R0.cmpt"},"geometricError":0.0}],"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D101_S002_T001_L0_U0_R0.cmpt"},"geometricError":0.0}],"geometricError":585.9375}],"geometricError":1171.875}],"geometricError":2343.75}],"geometricError":4687.5}],"geometricError":9375.0}],"geometricError":18750.0}],"geometricError":37500.0}],"geometricError":75000.0}],"geometricError":150000.0}],"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/Data/ImageryMoreLODNegativeElevation/VerifiedTileset.json b/Tests/Data/ImageryMoreLODNegativeElevation/VerifiedTileset.json index a47bb67..a745a12 100644 --- a/Tests/Data/ImageryMoreLODNegativeElevation/VerifiedTileset.json +++ b/Tests/Data/ImageryMoreLODNegativeElevation/VerifiedTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.050761871093337,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.059488517353309,0.5672320068981571,-2.050761871093337,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L00_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC01_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC02_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC03_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC04_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC05_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC06_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC07_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC08_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC09_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,146.697998046875]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0507618710933375,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R1.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0594885173533086,0.5672320068981571,-2.0507618710933375,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R1.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L0_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC1_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC2_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC3_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC4_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC5_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC6_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC7_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC8_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC9_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/Data/ImageryMoreLODPositiveElevation/VerifiedTileset.json b/Tests/Data/ImageryMoreLODPositiveElevation/VerifiedTileset.json index 52672f3..eb6d3e0 100644 --- a/Tests/Data/ImageryMoreLODPositiveElevation/VerifiedTileset.json +++ b/Tests/Data/ImageryMoreLODPositiveElevation/VerifiedTileset.json @@ -1 +1 @@ -{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.059488517353309,0.5585053606381855,-2.050761871093337,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.050761871093337,0.5585053606381855,-2.0463985479633515,0.5628686837681712,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U0_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5585053606381855,-2.0420352248333655,0.5628686837681712,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U0_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5628686837681712,-2.0463985479633515,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U1_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5628686837681712,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U1_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L01_U0_R1.b3dm"},"geometricError":146.484375},{"boundingVolume":{"region":[-2.059488517353309,0.5672320068981571,-2.050761871093337,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.050761871093337,0.5672320068981571,-2.0463985479633515,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U2_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5672320068981571,-2.0420352248333655,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U2_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.050761871093337,0.5715953300281429,-2.0463985479633515,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U3_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0463985479633515,0.5715953300281429,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L02_U3_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L01_U1_R1.b3dm"},"geometricError":146.484375}],"content":{"uri":"N32W118_D001_S001_T001_L00_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC01_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC02_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC03_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC04_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC05_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC06_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC07_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC08_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC09_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} +{"asset":{"version":"1.0"},"geometricError":300000.0,"root":{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0420352248333655,0.5759586531581288,-1.7796669006347656,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0594885173533086,0.5585053606381855,-2.0507618710933375,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5585053606381855,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"children":[{"boundingVolume":{"region":[-2.0507618710933375,0.5585053606381855,-2.046398547963351,0.5628686837681712,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U0_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5585053606381855,-2.0420352248333655,0.5628686837681712,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U0_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5628686837681712,-2.046398547963351,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U1_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5628686837681712,-2.0420352248333655,0.5672320068981571,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U1_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L1_U0_R1.b3dm"},"geometricError":146.484375},{"boundingVolume":{"region":[-2.0594885173533086,0.5672320068981571,-2.0507618710933375,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R0.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.0420352248333655,0.5759586531581288,-1.5761057138442993,475.4639892578125]},"children":[{"boundingVolume":{"region":[-2.0507618710933375,0.5672320068981571,-2.046398547963351,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U2_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5672320068981571,-2.0420352248333655,0.5715953300281429,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U2_R3.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.0507618710933375,0.5715953300281429,-2.046398547963351,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U3_R2.b3dm"},"geometricError":0.0},{"boundingVolume":{"region":[-2.046398547963351,0.5715953300281429,-2.0420352248333655,0.5759586531581288,0.0,0.0]},"content":{"uri":"N32W118_D001_S001_T001_L2_U3_R3.b3dm"},"geometricError":0.0}],"content":{"uri":"N32W118_D001_S001_T001_L1_U1_R1.b3dm"},"geometricError":146.484375}],"content":{"uri":"N32W118_D001_S001_T001_L0_U0_R0.b3dm"},"geometricError":292.96875}],"content":{"uri":"N32W118_D001_S001_T001_LC1_U0_R0.b3dm"},"geometricError":585.9375}],"content":{"uri":"N32W118_D001_S001_T001_LC2_U0_R0.b3dm"},"geometricError":1171.875}],"content":{"uri":"N32W118_D001_S001_T001_LC3_U0_R0.b3dm"},"geometricError":2343.75}],"content":{"uri":"N32W118_D001_S001_T001_LC4_U0_R0.b3dm"},"geometricError":4687.5}],"content":{"uri":"N32W118_D001_S001_T001_LC5_U0_R0.b3dm"},"geometricError":9375.0}],"content":{"uri":"N32W118_D001_S001_T001_LC6_U0_R0.b3dm"},"geometricError":18750.0}],"content":{"uri":"N32W118_D001_S001_T001_LC7_U0_R0.b3dm"},"geometricError":37500.0}],"content":{"uri":"N32W118_D001_S001_T001_LC8_U0_R0.b3dm"},"geometricError":75000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC9_U0_R0.b3dm"},"geometricError":150000.0}],"content":{"uri":"N32W118_D001_S001_T001_LC10_U0_R0.b3dm"},"geometricError":300000.0,"refine":"REPLACE"}} diff --git a/Tests/GltfTest.cpp b/Tests/GltfTest.cpp index 31b905c..290744f 100644 --- a/Tests/GltfTest.cpp +++ b/Tests/GltfTest.cpp @@ -1,5 +1,8 @@ #include "Gltf.h" +#include "Config.h" #include "catch2/catch.hpp" +#include +#include using namespace CDBTo3DTiles; @@ -318,3 +321,165 @@ TEST_CASE("Test converting multiple meshes to gltf", "[Gltf]") const auto &modelImage = modelImages.front(); REQUIRE(modelImage.uri == "textureURI"); } + +TEST_CASE("Test writing GLBs with JSON chunks padded to 8 bytes", "[Gltf]") +{ + // Create sample glTF. + Mesh triangleMesh = createTriangleMesh(); + tinygltf::Model model = createGltf(triangleMesh, nullptr, nullptr); + + // Write GLB. + std::filesystem::path glbPath = dataPath / "test.glb"; + std::ofstream fs(glbPath, std::ios::binary); + std::filesystem::current_path(dataPath); + writePaddedGLB(&model, fs); + fs.close(); + + // Read GLB. + std::ifstream inFile(glbPath, std::ios_base::binary); + inFile.seekg(0, std::ios_base::end); + size_t length = static_cast(inFile.tellg()); + inFile.seekg(0, std::ios_base::beg); + std::vector buffer; + buffer.reserve(length); + std::copy(std::istreambuf_iterator(inFile), + std::istreambuf_iterator(), + std::back_inserter(buffer)); + + // Read GLB length. + uint32_t glbLength; + std::memcpy(&glbLength, buffer.data() + 8, 4); + + // Read JSON chunk length. + uint32_t jsonChunkLength; + std::memcpy(&jsonChunkLength, buffer.data() + 12, 4); + + // Test padding of JSON chunk. + REQUIRE((20 + jsonChunkLength) % 8 == 0); + + // Remove output. + std::filesystem::remove_all(glbPath); +} + +TEST_CASE("Test combining GLBs", "[Gltf]") +{ + Mesh triangleMesh = createTriangleMesh(); + + SECTION("Test combining 0 glTFs") + { + // Create target glTF. + tinygltf::Model gltf; + gltf.asset.version = "2.0"; + tinygltf::Scene scene; + scene.nodes = { 0 }; + gltf.scenes.emplace_back(scene); + tinygltf::Buffer buffer; + gltf.buffers.emplace_back(buffer); + tinygltf::Node rootNode; + rootNode.matrix = {1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1}; + gltf.nodes.emplace_back(rootNode); + + combineGltfs(&gltf, {}); + + // Verify scenes. + std::vector expectedNodes = { 0 }; + REQUIRE(gltf.scenes.size() == 1); + REQUIRE(std::equal(gltf.scenes[0].nodes.begin(), gltf.scenes[0].nodes.end(), expectedNodes.begin())); + + // Verify nodes. + REQUIRE(gltf.nodes.size() == 1); + REQUIRE(gltf.nodes[0].children.size() == 0); + } + + + SECTION("Test combining multiple glTFs") + { + // Create source glTFs. + tinygltf::Model model1 = createGltf(triangleMesh, nullptr, nullptr); + tinygltf::Model model2 = createGltf(triangleMesh, nullptr, nullptr); + std::vector sourceGltfs = { model1, model2 }; + + // Create target glTF. + tinygltf::Model gltf; + gltf.asset.version = "2.0"; + tinygltf::Scene scene; + scene.nodes = { 0 }; + gltf.scenes.emplace_back(scene); + tinygltf::Buffer buffer; + gltf.buffers.emplace_back(buffer); + tinygltf::Node rootNode; + rootNode.matrix = {1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1}; + gltf.nodes.emplace_back(rootNode); + + combineGltfs(&gltf, sourceGltfs); + + // Verify scenes. + std::vector expectedNodes = { 0 }; + REQUIRE(gltf.scenes.size() == 1); + REQUIRE(std::equal(gltf.scenes[0].nodes.begin(), gltf.scenes[0].nodes.end(), expectedNodes.begin())); + + // Verify nodes. + std::vector expectedChildren = { 1, 2 }; + REQUIRE(gltf.nodes.size() == 3); + REQUIRE(gltf.nodes[0].children.size() == 2); + REQUIRE(std::equal(gltf.nodes[0].children.begin(), gltf.nodes[0].children.end(), expectedChildren.begin())); + for (size_t i = 1; i < gltf.nodes.size(); i++) { + auto node = gltf.nodes[i]; + + REQUIRE(node.mesh > -1); + + // Verify meshes. + auto mesh = gltf.meshes[static_cast(node.mesh)]; + REQUIRE(mesh.primitives.size() == 1); + + // Verify primitives. + auto primitive = mesh.primitives[0]; + REQUIRE(primitive.attributes["POSITION"] > -1); + REQUIRE(primitive.attributes["NORMAL"] > -1); + + // Verify accessors. + auto positionAccessor = gltf.accessors[static_cast(primitive.attributes["POSITION"])]; + REQUIRE(positionAccessor.bufferView > -1); + + auto normalAccessor = gltf.accessors[static_cast(primitive.attributes["NORMAL"])]; + REQUIRE(normalAccessor.bufferView > -1); + + // Verify accessor data. + auto positionBufferView = gltf.bufferViews[static_cast(positionAccessor.bufferView)]; + size_t positionAccessorStartOffset = positionAccessor.byteOffset + positionBufferView.byteOffset; + size_t positionAccessorByteLength = static_cast(positionAccessor.type * positionAccessor.componentType * static_cast(positionAccessor.count)); + std::vector positionData(gltf.buffers[0].data[positionAccessorStartOffset], gltf.buffers[0].data[positionAccessorStartOffset + positionAccessorByteLength]); + + auto normalBufferView = gltf.bufferViews[static_cast(normalAccessor.bufferView)]; + size_t normalAccessorStartOffset = normalAccessor.byteOffset + normalBufferView.byteOffset; + size_t normalAccessorByteLength = static_cast(normalAccessor.type * normalAccessor.componentType * static_cast(normalAccessor.count)); + std::vector normalData(gltf.buffers[0].data[normalAccessorStartOffset], gltf.buffers[0].data[normalAccessorStartOffset + normalAccessorByteLength]); + + auto sourceGltf = sourceGltfs[i - 1]; + auto sourcePositionAccessor = sourceGltf.accessors[static_cast(sourceGltf.meshes[0].primitives[0].attributes["POSITION"])]; + auto sourcePositionBufferView = sourceGltf.bufferViews[static_cast(sourcePositionAccessor.bufferView)]; + size_t sourcePositionAccessorStartOffset = sourcePositionAccessor.byteOffset + sourcePositionBufferView.byteOffset; + size_t sourcePositionAccessorByteLength = static_cast(sourcePositionAccessor.type * sourcePositionAccessor.componentType) * sourcePositionAccessor.count; + std::vector sourcePositionData(sourceGltf.buffers[0].data[sourcePositionAccessorStartOffset], sourceGltf.buffers[0].data[sourcePositionAccessorStartOffset + sourcePositionAccessorByteLength]); + + auto sourceNormalAccessor = sourceGltf.accessors[static_cast(sourceGltf.meshes[0].primitives[0].attributes["NORMAL"])]; + auto sourceNormalBufferView = sourceGltf.bufferViews[static_cast(sourceNormalAccessor.bufferView)]; + size_t sourceNormalAccessorStartOffset = sourceNormalAccessor.byteOffset + sourceNormalBufferView.byteOffset; + size_t sourceNormalAccessorByteLength = static_cast(sourceNormalAccessor.type * sourceNormalAccessor.componentType) * sourceNormalAccessor.count; + std::vector sourceNormalData(sourceGltf.buffers[0].data[sourceNormalAccessorStartOffset], sourceGltf.buffers[0].data[sourceNormalAccessorStartOffset + sourceNormalAccessorByteLength]); + + REQUIRE(std::equal( + positionData.begin(), + positionData.end(), + sourcePositionData.begin() + )); + + REQUIRE(std::equal( + normalData.begin(), + normalData.end(), + sourceNormalData.begin() + )); + + } + } +} \ No newline at end of file diff --git a/ThirdParty.json b/ThirdParty.json new file mode 100644 index 0000000..61ed4a5 --- /dev/null +++ b/ThirdParty.json @@ -0,0 +1,83 @@ +[ + { + "name": "earcut", + "license": [ + "ISC" + ], + "version": "0.12.3", + "url": "https://github.com/mapbox/earcut.hpp" + }, + { + "name": "meshoptimizer", + "license": [ + "MIT" + ], + "version": "0.16", + "url": "https://github.com/zeux/meshoptimizer" + }, + { + "name": "glm", + "license": [ + "MIT" + ], + "version": "0.9.9", + "url": "https://github.com/g-truc/glm" + }, + { + "name": "tinygltf", + "license": [ + "MIT" + ], + "version": "2.4.0", + "url": "https://github.com/syoyo/tinygltf" + }, + { + "name": "nlohmann_json", + "license": [ + "MIT" + ], + "version": "3.9.1", + "url": "https://github.com/nlohmann/json" + }, + { + "name": "cxxopts", + "license": [ + "MIT" + ], + "version": "2.2.1", + "url": "https://github.com/jarro2783/cxxopts" + }, + { + "name": "OpenSceneGraph", + "license": [ + "OpenSceneGraph Public License, Version 1.0" + ], + "version": "3.6.5", + "url": "https://github.com/openscenegraph/OpenSceneGraph", + "notes": "Contains static linking exception" + }, + { + "name": "libmorton", + "license": [ + "MIT" + ], + "version": "0.2.6", + "url": "https://github.com/Forceflow/libmorton" + }, + { + "name": "RapidXML", + "license": [ + "BSL-1.0" + ], + "version": "1.13", + "url": "http://rapidxml.sourceforge.net/" + }, + { + "name": "tiny-utf8", + "license": [ + "BSD-3-Clause" + ], + "version": "4.3", + "url": "https://github.com/DuffsDevice/tiny-utf8" + } +] \ No newline at end of file diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 78d22b0..1327af3 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -43,3 +43,15 @@ set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR} PARENT_SCOPE) set(nlohmann_json_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/nlohmann_json/include) set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_INCLUDE_DIR} PARENT_SCOPE) set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR} PARENT_SCOPE) + +set(LIBMORTON_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libmorton/libmorton) +set(LIBMORTON_INCLUDE_DIR ${LIBMORTON_INCLUDE_DIR} PARENT_SCOPE) +set(LIBMORTON_INCLUDE_DIRS ${LIBMORTON_INCLUDE_DIR} PARENT_SCOPE) + +set(rapidxml_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rapidxml) +set(rapidxml_INCLUDE_DIR ${rapidxml_INCLUDE_DIR} PARENT_SCOPE) +set(rapidxml_INCLUDE_DIRS ${rapidxml_INCLUDE_DIR} PARENT_SCOPE) + +set(tiny_utf8_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tiny-utf8/include) +set(tiny_utf8_INCLUDE_DIR ${tiny_utf8_INCLUDE_DIR} PARENT_SCOPE) +set(tiny_utf8_INCLUDE_DIRS ${tiny_utf8_INCLUDE_DIR} PARENT_SCOPE) \ No newline at end of file diff --git a/extern/OpenSceneGraph b/extern/OpenSceneGraph index a827840..312f640 160000 --- a/extern/OpenSceneGraph +++ b/extern/OpenSceneGraph @@ -1 +1 @@ -Subproject commit a827840baf0786d72e11ac16d5338a4ee25779db +Subproject commit 312f640a4ae831f5ad4c577d4ea14e616c6ae309 diff --git a/extern/libmorton b/extern/libmorton new file mode 160000 index 0000000..b8b0c2c --- /dev/null +++ b/extern/libmorton @@ -0,0 +1 @@ +Subproject commit b8b0c2c8fc78c144d1c48647ed1ab0fede9c4fda diff --git a/extern/meshoptimizer b/extern/meshoptimizer index 95893c0..ccd6fe4 160000 --- a/extern/meshoptimizer +++ b/extern/meshoptimizer @@ -1 +1 @@ -Subproject commit 95893c0566646434dd675b708d293fcb2d526d08 +Subproject commit ccd6fe48fff113e298427610dcf9ddced76ea32e diff --git a/extern/rapidxml b/extern/rapidxml new file mode 160000 index 0000000..f48e4d5 --- /dev/null +++ b/extern/rapidxml @@ -0,0 +1 @@ +Subproject commit f48e4d5da56c5efc4061d88934180650d3f47ad5 diff --git a/extern/tiny-utf8 b/extern/tiny-utf8 new file mode 160000 index 0000000..67305dc --- /dev/null +++ b/extern/tiny-utf8 @@ -0,0 +1 @@ +Subproject commit 67305dcc409e31d7e02cfcfc12c7e32738ee1017 diff --git a/extern/tinygltf b/extern/tinygltf index a159945..91da299 160000 --- a/extern/tinygltf +++ b/extern/tinygltf @@ -1 +1 @@ -Subproject commit a159945db9d97e79a30cb34e2aaa45fd28dea576 +Subproject commit 91da29972987bb4d715a09d94ecd2cefd3a487d4