diff --git a/.babelrc b/.babelrc index dd158be4a7..728dc8aae5 100644 --- a/.babelrc +++ b/.babelrc @@ -12,8 +12,8 @@ ["babel-plugin-inline-import", { "extensions": [ ".json", + ".geojson", ".glsl", - ".gltf", ".css" ] }], diff --git a/package-lock.json b/package-lock.json index b581e2440e..ff0dd82084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@babel/preset-env": "^7.22.5", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", + "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", "babel-loader": "^9.1.3", "babel-plugin-inline-import": "^3.0.0", @@ -56,6 +57,7 @@ "puppeteer": "^21.6.0", "q": "^1.5.1", "replace-in-file": "^7.0.2", + "sinon": "^17.0.1", "three": "^0.159.0", "typescript": "^5.3.3", "webpack": "^5.89.0", @@ -2461,6 +2463,50 @@ "dev": true, "license": "MIT" }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@tmcw/togeojson": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/@tmcw/togeojson/-/togeojson-5.8.1.tgz", @@ -2909,6 +2955,15 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -8287,6 +8342,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8441,6 +8502,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8970,6 +9037,55 @@ "node": ">= 0.4.0" } }, + "node_modules/nise": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -11384,6 +11500,45 @@ "dev": true, "license": "ISC" }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -12147,6 +12302,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.3.tgz", diff --git a/package.json b/package.json index 98721094d6..6ff0a21bcf 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@babel/preset-env": "^7.22.5", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", + "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", "babel-loader": "^9.1.3", "babel-plugin-inline-import": "^3.0.0", @@ -107,6 +108,7 @@ "puppeteer": "^21.6.0", "q": "^1.5.1", "replace-in-file": "^7.0.2", + "sinon": "^17.0.1", "three": "^0.159.0", "typescript": "^5.3.3", "webpack": "^5.89.0", diff --git a/src/Core/Style.js b/src/Core/Style.js index 3c714c1ec9..7c43d7075e 100644 --- a/src/Core/Style.js +++ b/src/Core/Style.js @@ -10,11 +10,10 @@ import itowns_stroke_single_before from './StyleChunk/itowns_stroke_single_befor export const cacheStyle = new Cache(); -const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); -const matrix = svg.createSVGMatrix(); +const matrix = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix(); +const canvas = document.createElement('canvas'); const inv255 = 1 / 255; -const canvas = (typeof document !== 'undefined') ? document.createElement('canvas') : {}; function baseAltitudeDefault(properties, ctx) { return ctx?.coordinates?.z || 0; @@ -1079,7 +1078,7 @@ const CustomStyle = { itowns_stroke_single_before, }; -const customStyleSheet = (typeof document !== 'undefined') ? document.createElement('style') : {}; +const customStyleSheet = document.createElement('style'); customStyleSheet.type = 'text/css'; Object.keys(CustomStyle).forEach((key) => { diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index 3fadfca3ec..ee48c5d62d 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -30,8 +30,8 @@ const update = process3dTilesNode(); /** * Find tileId of object - * * @param {THREE.Object3D} object - object + * * @returns {number} tileId */ function findTileID(object) { @@ -41,14 +41,13 @@ function findTileID(object) { currentObject = currentObject.parent; result = currentObject.tileId; } - return result; } /** * Check if object3d has feature - * * @param {THREE.Object3D} object3d - object3d to check + * * @returns {boolean} - true if object3d has feature */ function object3DHasFeature(object3d) { @@ -231,11 +230,13 @@ class C3DTilesLayer extends GeometryLayer { * targets picked under specified coordinates. Intersects can be * computed with view.pickObjectsAt(..). See fillHTMLWithPickingInfo() * in 3dTilesHelper.js for an example. + * * @returns {C3DTileFeature} - the closest C3DTileFeature of the intersects array */ getC3DTileFeatureFromIntersectsArray(intersects) { // find closest intersect with an attributes _BATCHID + face != undefined let closestIntersect = null; + for (let index = 0; index < intersects.length; index++) { const i = intersects[index]; if (i.object.geometry && @@ -277,7 +278,6 @@ class C3DTilesLayer extends GeometryLayer { /** * Initialize C3DTileFeatures from tileContent - * * @param {THREE.Object3D} tileContent - tile as THREE.Object3D */ initC3DTileFeatures(tileContent) { @@ -344,8 +344,8 @@ class C3DTilesLayer extends GeometryLayer { /** * Update style of the C3DTFeatures, an allowList of tile id can be passed to only update certain tile. * Note that this function only update THREE.Object3D materials, in order to see style changes you should call view.notifyChange() - * * @param {Array|null} [allowTileIdList] - tile ids to allow in updateStyle computation if null all tiles are updated + * * @returns {boolean} true if style updated false otherwise */ updateStyle(allowTileIdList = null) { diff --git a/src/Process/3dTilesProcessing.js b/src/Process/3dTilesProcessing.js index 44b2352db0..3bce32d607 100644 --- a/src/Process/3dTilesProcessing.js +++ b/src/Process/3dTilesProcessing.js @@ -272,7 +272,6 @@ export function process3dTilesNode(cullingTest = $3dTilesCulling, subdivisionTes return undefined; } - // do proper culling const isVisible = cullingTest ? (!cullingTest(layer, context.camera, node, node.matrixWorld)) : true; node.visible = isVisible; diff --git a/test/data/EGM2008_simplified.gdf b/test/data/EGM2008_simplified.gdf new file mode 100644 index 0000000000..13001c496c --- /dev/null +++ b/test/data/EGM2008_simplified.gdf @@ -0,0 +1,59 @@ + long_lat_unit degree + latlimit_north 90.000000000000 + latlimit_south -90.000000000000 + longlimit_west -180.00000000000 + longlimit_east 180.00000000000 + gridstep 45.000000000000 + latitude_parallels 5 + longitude_parallels 9 +number_of_gridpoints 45 + grid_format long_lat_value + + longitude latitude geoid + [deg.] [deg.] [meter] +end_of_head ============================================ + -180.0000 90.0000 15.303555275629 + -135.0000 90.0000 15.303555275629 + -90.0000 90.0000 15.303555275629 + -45.0000 90.0000 15.303555275629 + 0.0000 90.0000 15.303555275629 + 45.0000 90.0000 15.303555275629 + 90.0000 90.0000 15.303555275629 + 135.0000 90.0000 15.303555275629 + 180.0000 90.0000 15.303555275629 + -180.0000 45.0000 -5.932658849087 + -135.0000 45.0000 -26.975163013039 + -90.0000 45.0000 -33.177905776048 + -45.0000 45.0000 28.384029129025 + 0.0000 45.0000 47.171595586227 + 45.0000 45.0000 1.250168904938 + 90.0000 45.0000 -57.850806052736 + 135.0000 45.0000 28.732846840211 + 180.0000 45.0000 -5.932658849086 + -180.0000 0.0000 21.686023009812 + -135.0000 0.0000 -6.357450314494 + -90.0000 0.0000 -3.655670977408 + -45.0000 0.0000 -22.874077173170 + 0.0000 0.0000 17.630240691122 + 45.0000 0.0000 -43.169258390344 + 90.0000 0.0000 -62.880767797666 + 135.0000 0.0000 73.143720247498 + 180.0000 0.0000 21.686023009809 + -180.0000 -45.0000 3.784451421987 + -135.0000 -45.0000 -14.624611735984 + -90.0000 -45.0000 -0.474825154979 + -45.0000 -45.0000 -3.262856182088 + 0.0000 -45.0000 21.392469069597 + 45.0000 -45.0000 46.285391983932 + 90.0000 -45.0000 8.143232184390 + 135.0000 -45.0000 -19.858291708014 + 180.0000 -45.0000 3.784451421987 + -180.0000 -90.0000 -29.705396897506 + -135.0000 -90.0000 -29.705396897506 + -90.0000 -90.0000 -29.705396897506 + -45.0000 -90.0000 -29.705396897506 + 0.0000 -90.0000 -29.705396897506 + 45.0000 -90.0000 -29.705396897506 + 90.0000 -90.0000 -29.705396897506 + 135.0000 -90.0000 -29.705396897506 + 180.0000 -90.0000 -29.705396897506 diff --git a/test/data/OrientedImage/cameraCalibration.json b/test/data/OrientedImage/cameraCalibration.json new file mode 100644 index 0000000000..97efd4d7f4 --- /dev/null +++ b/test/data/OrientedImage/cameraCalibration.json @@ -0,0 +1,48 @@ +[ + { + "id": 300, + "rotation": [ + -0.00205265, + -0.999998, + 0.000732591, + 0.999997, + -0.00205156, + 0.00149035, + -0.00148884, + 0.000735648, + 0.999999 + ], + "position": [ + -0.000, + -0.145, + 0.867 + ], + "projection": [ + 1150.66785706630299, + 0, + 1030.29, + 0, + 1150.66785706630299, + 1024.96, + 0, + 0, + 1 + ], + "size": [ + 2048, + 2048 + ], + "distortion": { + "pps": [ + 1042.178, + 1027.565 + ], + "poly357": [ + -1.33791587603751819E-7, + 3.47540977328314388E-14, + -4.44103985918888078E-21 + ], + "limit": 2079 + } + } +] \ No newline at end of file diff --git a/test/data/OrientedImage/panoramicsMetaDataParis.geojson b/test/data/OrientedImage/panoramicsMetaDataParis.geojson new file mode 100644 index 0000000000..e950ede7ac --- /dev/null +++ b/test/data/OrientedImage/panoramicsMetaDataParis.geojson @@ -0,0 +1,32 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 651187.76, + 6861379.05, + 41.39 + ] + }, + "properties": { + "id": 482, + "easting": 651187.76, + "northing": 6861379.05, + "altitude": 41.39, + "heading": 176.117188, + "roll": 0.126007, + "pitch": 1.280821, + "date": "2014-06-16T12:31:34.841Z" + } + } + ], + "crs": { + "type": "EPSG", + "properties": { + "code": 2154 + } + } +} \ No newline at end of file diff --git a/test/data/entwine/ept-hierarchy/0-0-0-0.json b/test/data/entwine/ept-hierarchy/0-0-0-0.json new file mode 100644 index 0000000000..5c0e3d69f8 --- /dev/null +++ b/test/data/entwine/ept-hierarchy/0-0-0-0.json @@ -0,0 +1,14 @@ +{ + "0-0-0-0": 65341, + "1-0-0-0": 438, + "2-0-1-0": 322, + "1-0-0-1": 56209, + "2-0-1-2": 4332, + "2-1-1-2": 20300, + "2-1-1-3": 64020, + "3-2-3-6": -1, + "3-3-3-7": -1, + "1-0-1-0": 30390, + "2-1-2-0": 2300, + "1-1-1-1": 2303 +} \ No newline at end of file diff --git a/test/data/entwine/ept.json b/test/data/entwine/ept.json new file mode 100644 index 0000000000..d7e8ac2945 --- /dev/null +++ b/test/data/entwine/ept.json @@ -0,0 +1,34 @@ +{ + "bounds": [634962.0, 848881.0, -1818.0, 639620.0, 853539.0, 2840.0], + "boundsConforming": [635577.0, 848882.0, 406.0, 639004.0, 853538.0, 616.0], + "dataType": "laszip", + "hierarchyType": "json", + "points": 10653336, + "schema": [ + { "name": "X", "type": "signed", "size": 4, "scale": 0.01, "offset": 637291.0 }, + { "name": "Y", "type": "signed", "size": 4, "scale": 0.01, "offset": 851210.0 }, + { "name": "Z", "type": "signed", "size": 4, "scale": 0.01, "offset": 511.0 }, + { "name": "Intensity", "type": "unsigned", "size": 2 }, + { "name": "ReturnNumber", "type": "unsigned", "size": 1 }, + { "name": "NumberOfReturns", "type": "unsigned", "size": 1 }, + { "name": "ScanDirectionFlag", "type": "unsigned", "size": 1 }, + { "name": "EdgeOfFlightLine", "type": "unsigned", "size": 1 }, + { "name": "Classification", "type": "unsigned", "size": 1 }, + { "name": "ScanAngleRank", "type": "float", "size": 4 }, + { "name": "UserData", "type": "unsigned", "size": 1 }, + { "name": "PointSourceId", "type": "unsigned", "size": 2 }, + { "name": "GpsTime", "type": "float", "size": 8 }, + { "name": "Red", "type": "unsigned", "size": 2 }, + { "name": "Green", "type": "unsigned", "size": 2 }, + { "name": "Blue", "type": "unsigned", "size": 2 }, + { "name": "OriginId", "type": "unsigned", "size": 4 } + ], + "span" : 256, + "srs": { + "authority": "EPSG", + "horizontal": "3857", + "vertical": "5703", + "wkt": "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]" + }, + "version" : "1.0.0" +} \ No newline at end of file diff --git a/test/data/filesource/featCollec_Polygone.geojson b/test/data/filesource/featCollec_Polygone.geojson new file mode 100644 index 0000000000..0b5be2a9cb --- /dev/null +++ b/test/data/filesource/featCollec_Polygone.geojson @@ -0,0 +1,52 @@ +{ + "type": "FeatureCollection", + "name": "departement-09-ariege_simplified", + "features": [ + { + "type": "Feature", + "properties": { + "code": "09s", + "nom": "Ariège_simplified" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.832209677419342, + 42.908846774193506 + ], + [ + 1.531306451612893, + 43.271943548387057 + ], + [ + 1.959435483870959, + 43.087685483870928 + ], + [ + 2.154532258064508, + 42.662266129032218 + ], + [ + 1.786016129032249, + 42.580975806451569 + ], + [ + 0.886403225806439, + 42.80858870967738 + ], + [ + 0.886403225806439, + 42.80858870967738 + ], + [ + 0.832209677419342, + 42.908846774193506 + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/test/data/filesource/feat_Polygone.geojson b/test/data/filesource/feat_Polygone.geojson new file mode 100644 index 0000000000..a5a7ad1fbd --- /dev/null +++ b/test/data/filesource/feat_Polygone.geojson @@ -0,0 +1,46 @@ +{ + "type": "Feature", + "properties": { + "code": "09s", + "nom": "Ariège_simplified" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.832209677419342, + 42.908846774193506 + ], + [ + 1.531306451612893, + 43.271943548387057 + ], + [ + 1.959435483870959, + 43.087685483870928 + ], + [ + 2.154532258064508, + 42.662266129032218 + ], + [ + 1.786016129032249, + 42.580975806451569 + ], + [ + 0.886403225806439, + 42.80858870967738 + ], + [ + 0.886403225806439, + 42.80858870967738 + ], + [ + 0.832209677419342, + 42.908846774193506 + ] + ] + ] + } +} \ No newline at end of file diff --git a/test/data/geojson/gpx.geojson.json b/test/data/geojson/gpx.geojson similarity index 100% rename from test/data/geojson/gpx.geojson.json rename to test/data/geojson/gpx.geojson diff --git a/test/data/geojson/holes.geojson.json b/test/data/geojson/holes.geojson similarity index 100% rename from test/data/geojson/holes.geojson.json rename to test/data/geojson/holes.geojson diff --git a/test/data/geojson/holesPoints.geojson.json b/test/data/geojson/holesPoints.geojson similarity index 100% rename from test/data/geojson/holesPoints.geojson.json rename to test/data/geojson/holesPoints.geojson diff --git a/test/data/geojson/map.geojson.json b/test/data/geojson/map.geojson similarity index 100% rename from test/data/geojson/map.geojson.json rename to test/data/geojson/map.geojson diff --git a/test/data/geojson/map_big.geojson.json b/test/data/geojson/map_big.geojson similarity index 100% rename from test/data/geojson/map_big.geojson.json rename to test/data/geojson/map_big.geojson diff --git a/test/data/geojson/map_small.geojson.json b/test/data/geojson/map_small.geojson similarity index 100% rename from test/data/geojson/map_small.geojson.json rename to test/data/geojson/map_small.geojson diff --git a/test/data/geojson/points.geojson.json b/test/data/geojson/points.geojson similarity index 100% rename from test/data/geojson/points.geojson.json rename to test/data/geojson/points.geojson diff --git a/test/data/geojson/simple.geojson.json b/test/data/geojson/simple.geojson similarity index 100% rename from test/data/geojson/simple.geojson.json rename to test/data/geojson/simple.geojson diff --git a/test/data/raf09_simplified.isg b/test/data/raf09_simplified.isg new file mode 100644 index 0000000000..3197ab327d --- /dev/null +++ b/test/data/raf09_simplified.isg @@ -0,0 +1,21 @@ +begin_of_head ================================================ +model name : RAF09 +model type : hybrid +units : meters +reference : GRS80 +lat min = 41.9875 +lat max = 51.5125 +lon min = -5.5167 +lon max = 8.5167 +delta lat = 1.9050 +delta lon = 0.0242 +nrows = 5 +ncols = 7 +nodata = -9999.0000 +ISG format = 1.0 +end_of_head ================================================== + 50.0000 55.0000 60.0000 65.0000 70.0000 75.0000 80.0000 + 55.0000 60.0000 65.0000 70.0000 75.0000 80.0000 85.0000 + 60.0000 65.0000 70.0000 75.0000 80.0000 85.0000 90.0000 + 65.0000 70.0000 75.0000 80.0000 85.0000 90.0000 95.0000 + 70.0000 75.0000 80.0000 85.0000 90.0000 95.0000 100.000 diff --git a/test/data/vectortiles/sprite.json b/test/data/vectortiles/sprite.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/test/data/vectortiles/sprite.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/data/vectortiles/style.json b/test/data/vectortiles/style.json new file mode 100644 index 0000000000..1af58000a3 --- /dev/null +++ b/test/data/vectortiles/style.json @@ -0,0 +1,25 @@ +{ + "sources": { + "sourceName": { + "url": "https://test/tilejson.json" + } + }, + "sprite": "https://test/sprite", + "layers": [ + { + "type": "background", + "paint": { + "fill-color": "#0000ff" + } + }, + { + "id": "land", + "type": "fill", + "minzoom": 5, + "maxzoom": 13, + "paint": { + "fill-color": "rgb(255, 0, 0)" + } + } + ] +} \ No newline at end of file diff --git a/test/data/vectortiles/tilejson.json b/test/data/vectortiles/tilejson.json new file mode 100644 index 0000000000..8bb15eed0d --- /dev/null +++ b/test/data/vectortiles/tilejson.json @@ -0,0 +1,5 @@ +{ + "tiles": [ + "http://server.geo/vectortiles/{z}/{x}/{y}" + ] +} \ No newline at end of file diff --git a/test/functional/3dtiles_batch_table.js b/test/functional/3dtiles_batch_table.js index 07dfaf3e7f..6852bea081 100644 --- a/test/functional/3dtiles_batch_table.js +++ b/test/functional/3dtiles_batch_table.js @@ -29,7 +29,7 @@ describe('3dtiles_batch_table', function _() { // Verifies that the batch id, batch table and batch table hierarchy // extension picked information are correct for object at { x: 218, y: 90 } it('should return the batch table and batch hierarchy picked information', - async () => { + async function _it() { // Picks the object at (218,90) and gets its closest c3DTileFeature const pickResult = await page.evaluate( () => { @@ -70,8 +70,8 @@ describe('3dtiles_batch_table', function _() { }, }, }; - const expectedBatchId = 29; - assert.deepStrictEqual(pickResult.batchId, expectedBatchId); + + assert.equal(pickResult.batchId, 29); assert.deepStrictEqual(pickResult.info, expectedPickingInfo); }); }); diff --git a/test/unit/3dtileslayerprocess.js b/test/unit/3dtileslayerprocess.js index a0149b1509..95176d25e2 100644 --- a/test/unit/3dtileslayerprocess.js +++ b/test/unit/3dtileslayerprocess.js @@ -1,50 +1,89 @@ import assert from 'assert'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import C3DTilesLayer from 'Layer/C3DTilesLayer'; import C3DTilesSource from 'Source/C3DTilesSource'; import View from 'Core/View'; import GlobeView from 'Core/Prefab/GlobeView'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Coordinates from 'Core/Geographic/Coordinates'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +const url = 'https://raw.githubusercontent.com/CesiumGS/3d-tiles-samples/main/1.0/TilesetWithDiscreteLOD'; +const tilesetUrl = `${url}/tileset.json`; +const b3dmUrl = `${url}/dragon_low.b3dm`; + describe('3Dtiles layer', function () { - const renderer = new Renderer(); - - const p = { coord: new Coordinates('EPSG:4326', -75.6114, 40.03428, 0), heading: 180, range: 4000, tilt: 22 }; - - const viewer = new GlobeView(renderer.domElement, p, { renderer, noControls: true }); - - const threedTilesLayer = new C3DTilesLayer('3d-tiles-discrete-lod', { - source: new C3DTilesSource({ - url: 'https://raw.githubusercontent.com/CesiumGS/3d-tiles-samples/master/1.0/TilesetWithDiscreteLOD/tileset.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }), - sseThreshold: 0.05, - }, viewer); - - const context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: viewer.mainLoop.scheduler, - geometryLayer: threedTilesLayer, - view: viewer, - }; - - it('Add 3dtiles layer', function (done) { - View.prototype.addLayer.call(viewer, threedTilesLayer) - .then((layer) => { - assert.equal(layer.root.children.length, 1); - done(); - }).catch(done); - }); - it('preUpdate 3dtiles layer', function () { - const elements = threedTilesLayer.preUpdate(context, new Set([threedTilesLayer])); - assert.equal(elements.length, 1); + let tileset; + let b3dm; + let dataFetched; + it('fetch binaries', async function () { + const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; + tileset = await Fetcher.json(tilesetUrl, networkOptions); + b3dm = await Fetcher.arrayBuffer(b3dmUrl, networkOptions); + dataFetched = true; }); - it('update 3dtiles layer', function () { - const node = threedTilesLayer.root; - viewer.camera3D.updateMatrixWorld(); - threedTilesLayer.update(context, threedTilesLayer, node); - assert.ok(node.pendingSubdivision); + + describe('unit tests', function () { + let stubFetcherArrayBuf; + let stubFetcherJson; + let context; + let viewer; + let threedTilesLayer; + + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(() => Promise.resolve(tileset)); + + stubFetcherArrayBuf = sinon.stub(Fetcher, 'arrayBuffer') + .callsFake(() => Promise.resolve(b3dm)); + + if (!dataFetched) { this.skip(); } + + const renderer = new Renderer(); + const p = { coord: new Coordinates('EPSG:4326', -75.6114, 40.03428, 0), heading: 180, range: 4000, tilt: 22 }; + viewer = new GlobeView(renderer.domElement, p, { renderer, noControls: true }); + + const source = new C3DTilesSource({ + url: 'https://raw.githubusercontent.com/CesiumGS/3d-tiles-samples/master/1.0/TilesetWithDiscreteLOD/tileset.json', + }); + threedTilesLayer = new C3DTilesLayer('3d-tiles-discrete-lod', { + source, + sseThreshold: 0.05, + }, viewer); + + context = { + camera: viewer.camera, + engine: viewer.mainLoop.gfxEngine, + scheduler: viewer.mainLoop.scheduler, + geometryLayer: threedTilesLayer, + view: viewer, + }; + }); + + after(() => { + stubFetcherArrayBuf.restore(); + stubFetcherJson.restore(); + }); + + it('Add 3dtiles layer', function (done) { + View.prototype.addLayer.call(viewer, threedTilesLayer) + .then((layer) => { + assert.equal(layer.root.children.length, 1); + done(); + }).catch(done); + }); + + it('preUpdate 3dtiles layer', function () { + const elements = threedTilesLayer.preUpdate(context, new Set([threedTilesLayer])); + assert.equal(elements.length, 1); + }); + + it('update 3dtiles layer', function () { + const node = threedTilesLayer.root; + viewer.camera3D.updateMatrixWorld(); + threedTilesLayer.update(context, threedTilesLayer, node); + assert.ok(node.pendingSubdivision); + }); }); }); diff --git a/test/unit/3dtileslayerprocessbatchtable.js b/test/unit/3dtileslayerprocessbatchtable.js index 4a90ee87fd..b4a2752acf 100644 --- a/test/unit/3dtileslayerprocessbatchtable.js +++ b/test/unit/3dtileslayerprocessbatchtable.js @@ -1,4 +1,5 @@ import assert from 'assert'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import C3DTilesLayer from 'Layer/C3DTilesLayer'; import C3DTBatchTableHierarchyExtension from 'Core/3DTiles/C3DTBatchTableHierarchyExtension'; import C3DTilesSource from 'Source/C3DTilesSource'; @@ -6,44 +7,79 @@ import C3DTExtensions from 'Core/3DTiles/C3DTExtensions'; import { C3DTilesTypes } from 'Core/3DTiles/C3DTilesEnums'; import View from 'Core/View'; import GlobeView from 'Core/Prefab/GlobeView'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Coordinates from 'Core/Geographic/Coordinates'; +import Fetcher from 'Provider/Fetcher'; +import sinon from 'sinon'; import Renderer from './bootstrap'; +const url = 'https://raw.githubusercontent.com/AnalyticalGraphicsInc/cesium/master/Apps/SampleData/Cesium3DTiles/Hierarchy/BatchTableHierarchy'; +const b3dmUrl = `${url}/tile.b3dm`; +const tilesetUrl = `${url}/tileset.json`; + describe('3Dtiles batch table', function () { - const renderer = new Renderer(); - - const p = { - coord: new Coordinates('EPSG:4326', -75.61349, 40.044259), - range: 200, - tilt: 10, - heading: -145, - }; - const viewer = new GlobeView(renderer.domElement, p, { renderer, noControls: true }); - - // Map the extension name to its manager - const extensions = new C3DTExtensions(); - extensions.registerExtension('3DTILES_batch_table_hierarchy', - { [C3DTilesTypes.batchtable]: + let tileset; + let b3dm; + let dataFetched; + it('fetch binaries', async function () { + const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; + tileset = await Fetcher.json(tilesetUrl, networkOptions); + b3dm = await Fetcher.arrayBuffer(b3dmUrl, networkOptions); + dataFetched = true; + }); + + describe('unit tests', function () { + let stubFetcherArrayBuf; + let stubFetcherJson; + let viewer; + let threedTilesLayerBTHierarchy; + + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(() => Promise.resolve(tileset)); + + stubFetcherArrayBuf = sinon.stub(Fetcher, 'arrayBuffer') + .callsFake(() => Promise.resolve(b3dm)); + + if (!dataFetched) { this.skip(); } + + const renderer = new Renderer(); + const p = { + coord: new Coordinates('EPSG:4326', -75.61349, 40.044259), + range: 200, + tilt: 10, + heading: -145, + }; + viewer = new GlobeView(renderer.domElement, p, { renderer, noControls: true }); + + // Map the extension name to its manager + const extensions = new C3DTExtensions(); + extensions.registerExtension('3DTILES_batch_table_hierarchy', + { [C3DTilesTypes.batchtable]: C3DTBatchTableHierarchyExtension }); - const threedTilesLayerBTHierarchy = new C3DTilesLayer( - '3d-tiles-bt-hierarchy', { - name: 'BTHierarchy', - source: new C3DTilesSource({ + const source = new C3DTilesSource({ url: 'https://raw.githubusercontent.com/AnalyticalGraphicsInc/cesium/master/Apps/SampleData/Cesium3DTiles/Hierarchy/BatchTableHierarchy/tileset.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }), - registeredExtensions: extensions, - }, viewer); - - it('Add 3dtiles layer with batch table', function (done) { - View.prototype.addLayer.call(viewer, threedTilesLayerBTHierarchy) - .then((layer) => { - assert.equal(layer.root.children.length, 1); - const batchLength = viewer.getLayerById('3d-tiles-bt-hierarchy').root.batchTable.batchLength; - assert.equal(batchLength, 30); - done(); - }).catch(done); + }); + threedTilesLayerBTHierarchy = new C3DTilesLayer('3d-tiles-bt-hierarchy', { + name: 'BTHierarchy', + source, + registeredExtensions: extensions, + }, viewer); + }); + + after(() => { + stubFetcherJson.restore(); + stubFetcherArrayBuf.restore(); + }); + + it('Add 3dtiles layer with batch table', function (done) { + View.prototype.addLayer.call(viewer, threedTilesLayerBTHierarchy) + .then((layer) => { + assert.equal(layer.root.children.length, 1); + const batchLength = viewer.getLayerById('3d-tiles-bt-hierarchy').root.batchTable.batchLength; + assert.equal(batchLength, 30); + done(); + }).catch(done); + }); }); }); diff --git a/test/unit/3dtileslayerstyle.js b/test/unit/3dtileslayerstyle.js index 327b62a382..4c78899983 100644 --- a/test/unit/3dtileslayerstyle.js +++ b/test/unit/3dtileslayerstyle.js @@ -1,85 +1,108 @@ import assert from 'assert'; import proj4 from 'proj4'; import * as THREE from 'three'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Extent from 'Core/Geographic/Extent'; import PlanarView from 'Core/Prefab/PlanarView'; import C3DTBatchTable from 'Core/3DTiles/C3DTBatchTable'; import C3DTilesSource from 'Source/C3DTilesSource'; import C3DTilesLayer from 'Layer/C3DTilesLayer'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +const tileset = {}; + +// Create a 'fake' tile content for this test purpose +function createTileContent(tileId) { + const geometry = new THREE.SphereGeometry(15, 32, 16); + const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); + + // Add _BATCHID geometry attributes + const array = []; + let currentBatchId = Math.round(Math.random() * 50); + for (let index = 0; index < geometry.attributes.position.count; index++) { + array.push(currentBatchId); + + // Change randomly batch id + if (Math.random() > 0.5) { + currentBatchId = Math.round(Math.random() * 50); + } + } + geometry.setAttribute('_BATCHID', new THREE.BufferAttribute(Int32Array.from(array), 1)); + + const result = new THREE.Mesh(geometry, material); + result.batchTable = new C3DTBatchTable(); + result.tileId = tileId; + + return result; +} + describe('3DTilesLayer Style', () => { - // Define crs - proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); + let view; + let $3dTilesLayer; + let source; + let stubFetcherJson; + + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(() => Promise.resolve(tileset)); + + // Define crs + proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); + + // Define geographic extent: CRS, min/max X, min/max Y + const extent = new Extent('EPSG:3946', + 1840816.94334, 1843692.32501, + 5175036.4587, 5177412.82698); - // Define geographic extent: CRS, min/max X, min/max Y - const extent = new Extent('EPSG:3946', - 1840816.94334, 1843692.32501, - 5175036.4587, 5177412.82698); + const renderer = new Renderer(); - const renderer = new Renderer(); + view = new PlanarView(renderer.domElement, extent, { renderer, noControls: true }); + }); - const view = new PlanarView(renderer.domElement, extent, { renderer, noControls: true }); + after(function () { + stubFetcherJson.restore(); + }); - const $3dTilesLayer = new C3DTilesLayer( - 'id_layer', - { - source: new C3DTilesSource({ - url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + + it('should instance C3DTilesLayer', function (done) { + source = new C3DTilesSource({ + url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + '3DTiles/lyon1_with_surface_type_2018/tileset.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }), - }, - view, - ); - - $3dTilesLayer.style = { - fill: { - color: (c3DTileFeature) => { - if (c3DTileFeature.batchId > 1) { - return 'red'; - } else { - return 'blue'; - } - }, - opacity: (c3DTileFeature) => { - if (c3DTileFeature.getInfo().something) { - return 0.1; - } else if (c3DTileFeature.userData.something === 'random') { - return 1; - } else { - return 0.5; - } - }, - }, - }; - - // Create a 'fake' tile content for this test purpose - const createTileContent = (tileId) => { - const geometry = new THREE.SphereGeometry(15, 32, 16); - const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); - - // Add _BATCHID geometry attributes - const array = []; - let currentBatchId = Math.round(Math.random() * 50); - for (let index = 0; index < geometry.attributes.position.count; index++) { - array.push(currentBatchId); - - // Change randomly batch id - if (Math.random() > 0.5) { - currentBatchId = Math.round(Math.random() * 50); - } - } - geometry.setAttribute('_BATCHID', new THREE.BufferAttribute(Int32Array.from(array), 1)); + }); - const result = new THREE.Mesh(geometry, material); + $3dTilesLayer = new C3DTilesLayer('id_layer', + { source }, + view, + ); - result.batchTable = new C3DTBatchTable(); - result.tileId = tileId; + $3dTilesLayer.style = { + fill: { + color: (c3DTileFeature) => { + if (c3DTileFeature.batchId > 1) { + return 'red'; + } else { + return 'blue'; + } + }, + opacity: (c3DTileFeature) => { + if (c3DTileFeature.getInfo().something) { + return 0.1; + } else if (c3DTileFeature.userData.something === 'random') { + return 1; + } else { + return 0.5; + } + }, + }, + }; - return result; - }; + source.whenReady + .then(() => { + assert.ok(source.isC3DTilesSource); + done(); + }) + .catch(done); + }); it('Load tile content', function () { for (let index = 0; index < 10; index++) { diff --git a/test/unit/bootstrap.js b/test/unit/bootstrap.js index 6f90d649d3..19a23bb776 100644 --- a/test/unit/bootstrap.js +++ b/test/unit/bootstrap.js @@ -1,10 +1,12 @@ /* eslint-disable max-classes-per-file */ import fetch from 'node-fetch'; import { Camera } from 'three'; +import { DOMParser } from '@xmldom/xmldom'; global.window = { addEventListener: () => {}, removeEventListener: () => {}, + DOMParser, setTimeout, }; @@ -34,6 +36,31 @@ if (!global.navigator) { }; } +const HEADER_SIZE = 40; +const GTX_ROWS = 381; +const GTX_COLS = 421; + +global.createGtxBuffer = function (elevation = 1) { + const buffer = new ArrayBuffer( + HEADER_SIZE + GTX_COLS * GTX_ROWS * Float32Array.BYTES_PER_ELEMENT, + ); + + const header = new DataView(buffer, 0, HEADER_SIZE); + header.setFloat64(0, 42); + header.setFloat64(8, -5.5); + header.setFloat64(16, 0.025); + header.setFloat64(24, 0.0333333333333); + header.setInt32(32, GTX_ROWS); + header.setInt32(36, GTX_COLS); + + const data = new DataView(buffer, 40); + for (let i = 0; i < data.byteLength; i += Float32Array.BYTES_PER_ELEMENT) { + data.setFloat32(i, elevation); + } + + return buffer; +}; + class DOMElement { constructor() { this.children = []; diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index b1a1fe435f..e43b216295 100644 --- a/test/unit/dataSourceProvider.js +++ b/test/unit/dataSourceProvider.js @@ -6,7 +6,7 @@ import TileMesh from 'Core/TileMesh'; import Extent, { globalExtentTMS } from 'Core/Geographic/Extent'; import OBB from 'Renderer/OBB'; import DataSourceProvider from 'Provider/DataSourceProvider'; -import { supportedFetchers, supportedParsers } from 'Source/Source'; +import { supportedFetchers } from 'Source/Source'; import TileProvider from 'Provider/TileProvider'; import WMTSSource from 'Source/WMTSSource'; import WMSSource from 'Source/WMSSource'; @@ -20,27 +20,36 @@ import Style from 'Core/Style'; import Feature2Mesh from 'Converter/Feature2Mesh'; import LayeredMaterial from 'Renderer/LayeredMaterial'; import { EMPTY_TEXTURE_ZOOM } from 'Renderer/RasterTile'; +import sinon from 'sinon'; -const holes = require('../data/geojson/holesPoints.geojson.json'); +import holes from '../data/geojson/holesPoints.geojson'; + +const stubSupportedFetchers = new Map([ + ['application/json', () => Promise.resolve(JSON.parse(holes))], + ['image/png', () => Promise.resolve(new THREE.Texture())], +]); describe('Provide in Sources', function () { - // /!\ Avoid to overload fetcher because could troubleshoot the other unit tests? - // formatTag to avoid it - const formatTag = 'dspUnitTest'; - supportedFetchers.set(`${formatTag}image/png`, () => Promise.resolve(new THREE.Texture())); - supportedFetchers.set(`${formatTag}application/json`, () => Promise.resolve(holes)); - supportedParsers.set(`${formatTag}image/png`, supportedParsers.get('image/png')); - supportedParsers.set(`${formatTag}application/json`, supportedParsers.get('application/json')); + // TODO We should mock the creation of all layers creation. // Misc var to initialize a TileMesh instance const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); const globalExtent = globalExtentTMS.get('EPSG:3857'); + const material = new LayeredMaterial(); const zoom = 10; const sizeTile = globalExtent.planarDimensions().x / 2 ** zoom; const extent = new Extent('EPSG:3857', 0, sizeTile, 0, sizeTile); - // const zoom = 4; - const material = new LayeredMaterial(); + + let stub; + let planarlayer; + let elevationlayer; + let colorlayer; + let nodeLayer; + let nodeLayerElevation; + let featureLayer; + + let featureCountByCb = 0; // Mock scheduler const context = { @@ -56,52 +65,61 @@ describe('Provide in Sources', function () { }, }; - const planarlayer = new PlanarLayer('globe', globalExtent, new THREE.Group()); - const colorlayer = new ColorLayer('color', { crs: 'EPSG:3857', source: false }); - const elevationlayer = new ElevationLayer('elevation', { crs: 'EPSG:3857', source: false }); - - planarlayer.attach(colorlayer); - planarlayer.attach(elevationlayer); - - const fakeNode = { material, setBBoxZ: () => {}, addEventListener: () => {} }; - colorlayer.setupRasterNode(fakeNode); - elevationlayer.setupRasterNode(fakeNode); - - const nodeLayer = material.getLayer(colorlayer.id); - const nodeLayerElevation = material.getLayer(elevationlayer.id); - - const featureLayer = new GeometryLayer('geom', new THREE.Group(), { - update: FeatureProcessing.update, - convert: Feature2Mesh.convert(), - crs: 'EPSG:4978', - mergeFeatures: false, - zoom: { min: 10 }, - source: false, - style: new Style({ - fill: { - extrusion_height: 5000, - color: new THREE.Color(0xffcc00), - }, - }), - }); + before(function () { + stub = sinon.stub(supportedFetchers, 'get') + .callsFake(format => stubSupportedFetchers.get(format)); - featureLayer.source = new WFSSource({ - url: 'http://', - typeName: 'name', - format: `${formatTag}application/json`, - extent: globalExtent, - crs: 'EPSG:3857', - }); + planarlayer = new PlanarLayer('globe', globalExtent, new THREE.Group()); + colorlayer = new ColorLayer('color', { crs: 'EPSG:3857', source: false }); + elevationlayer = new ElevationLayer('elevation', { crs: 'EPSG:3857', source: false }); - let featureCountByCb = 0; - featureLayer.source.onParsedFile = (fc) => { - featureCountByCb = fc.features.length; - }; + planarlayer.attach(colorlayer); + planarlayer.attach(elevationlayer); - planarlayer.attach(featureLayer); + const fakeNode = { material, setBBoxZ: () => {}, addEventListener: () => {} }; + colorlayer.setupRasterNode(fakeNode); + elevationlayer.setupRasterNode(fakeNode); + + nodeLayer = material.getLayer(colorlayer.id); + nodeLayerElevation = material.getLayer(elevationlayer.id); + + featureLayer = new GeometryLayer('geom', new THREE.Group(), { + update: FeatureProcessing.update, + convert: Feature2Mesh.convert(), + crs: 'EPSG:4978', + mergeFeatures: false, + zoom: { min: 10 }, + source: false, + style: new Style({ + fill: { + extrusion_height: 5000, + color: new THREE.Color(0xffcc00), + }, + }), + }); + + featureLayer.source = new WFSSource({ + url: 'http://', + typeName: 'name', + format: 'application/json', + extent: globalExtent, + crs: 'EPSG:3857', + }); + + featureLayer.source.onParsedFile = (fc) => { + featureCountByCb = fc.features.length; + }; + + planarlayer.attach(featureLayer); + + context.elevationLayers = [elevationlayer]; + context.colorLayers = [colorlayer]; + }); + + after(function () { + stub.restore(); + }); - context.elevationLayers = [elevationlayer]; - context.colorLayers = [colorlayer]; beforeEach('reset state', function () { // clear commands array @@ -112,7 +130,7 @@ describe('Provide in Sources', function () { colorlayer.source = new WMTSSource({ url: 'http://', name: 'name', - format: `${formatTag}image/png`, + format: 'image/png', tileMatrixSet: 'PM', crs: 'EPSG:3857', extent: globalExtent, @@ -144,7 +162,7 @@ describe('Provide in Sources', function () { elevationlayer.source = new WMTSSource({ url: 'http://', name: 'name', - format: `${formatTag}image/png`, + format: 'image/png', tileMatrixSet: 'PM', crs: 'EPSG:3857', zoom: { @@ -173,7 +191,7 @@ describe('Provide in Sources', function () { colorlayer.source = new WMSSource({ url: 'http://', name: 'name', - format: `${formatTag}image/png`, + format: 'image/png', extent: globalExtent, crs: 'EPSG:3857', zoom: { @@ -306,7 +324,7 @@ describe('Provide in Sources', function () { colorlayer.source = new WMTSSource({ url: 'http://', name: 'name', - format: `${formatTag}image/png`, + format: 'image/png', tileMatrixSet: 'PM', crs: 'EPSG:3857', extent: globalExtent, diff --git a/test/unit/demutils.js b/test/unit/demutils.js index 7505fa11ff..cfa5193688 100644 --- a/test/unit/demutils.js +++ b/test/unit/demutils.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; import ElevationLayer from 'Layer/ElevationLayer'; import WMTSSource from 'Source/WMTSSource'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import { supportedFetchers } from 'Source/Source'; import assert from 'assert'; import GlobeView from 'Core/Prefab/GlobeView'; import Coordinates from 'Core/Geographic/Coordinates'; @@ -12,39 +12,63 @@ import OBB from 'Renderer/OBB'; import LayerUpdateState from 'Layer/LayerUpdateState'; import DEMUtils from 'Utils/DEMUtils'; import { RasterElevationTile } from 'Renderer/RasterTile'; +import sinon from 'sinon'; import Renderer from './bootstrap'; +const BIL_ROWS = 256; +const BIL_COLS = 256; +function createBilData(elevation = 1) { + return new Float32Array(new Array(BIL_COLS * BIL_ROWS).fill(elevation)); +} + describe('DemUtils', function () { const renderer = new Renderer(); const placement = { coord: new Coordinates('EPSG:4326', 1.5, 43), zoom: 10 }; const viewer = new GlobeView(renderer.domElement, placement, { renderer }); - const source = new WMTSSource({ - format: 'image/x-bil;bits=32', - crs: 'EPSG:4326', - url: 'https://data.geopf.fr/wmts?', - name: 'ELEVATION.ELEVATIONGRIDCOVERAGE.SRTM3', - tileMatrixSet: 'WGS84G', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : { - // referrerPolicy: 'origin-when-cross-origin', - crossOrigin: 'anonymous', - // referrer: 'http://localhost:8080/examples/view_3d_map.html', - }, - }); - source.url = 'https://github.com/iTowns/iTowns2-sample-data/blob/master/dem3_3_8.bil?raw=true'; - const elevationlayer = new ElevationLayer('worldelevation', { source }); + let elevationlayer; + let context; + let stubSuppFetcher; + const ELEVATION = 300; + + before(function () { + stubSuppFetcher = sinon.stub(supportedFetchers, 'get'); + stubSuppFetcher.withArgs('image/x-bil;bits=32') + .callsFake(() => { + const floatArray = createBilData(ELEVATION); + + const texture = new THREE.DataTexture(floatArray, 256, 256, THREE.RedFormat, THREE.FloatType); + texture.internalFormat = 'R32F'; + texture.needsUpdate = false; + return () => Promise.resolve(texture); + }); + + const source = new WMTSSource({ + format: 'image/x-bil;bits=32', + crs: 'EPSG:4326', + url: 'https://data.geopf.fr/wmts?', + name: 'ELEVATION.ELEVATIONGRIDCOVERAGE.SRTM3', + tileMatrixSet: 'WGS84G', + }); + source.url = 'https://github.com/iTowns/iTowns2-sample-data/blob/master/dem3_3_8.bil?raw=true'; + elevationlayer = new ElevationLayer('worldelevation', { source }); - const context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: { - execute: (command) => { - const provider = viewer.mainLoop.scheduler.getProtocolProvider(command.layer.protocol); - return provider.executeCommand(command); + context = { + camera: viewer.camera, + engine: viewer.mainLoop.gfxEngine, + scheduler: { + execute: (command) => { + const provider = viewer.mainLoop.scheduler.getProtocolProvider(command.layer.protocol); + return provider.executeCommand(command); + }, }, - }, - view: viewer, - }; + view: viewer, + }; + }); + + after(() => { + stubSuppFetcher.restore(); + }); it('add elevation layer', (done) => { viewer.addLayer(elevationlayer) @@ -56,6 +80,7 @@ describe('DemUtils', function () { const tiles = []; const extent = new Extent('EPSG:4326', 5.625, 11.25, 45, 50.625); const coord = extent.center(); + it('load elevation texture', (done) => { const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); @@ -67,29 +92,29 @@ describe('DemUtils', function () { tiles.push(tile); updateLayeredMaterialNodeElevation(context, elevationlayer, tile, {}) .then(() => { - assert.equal(nodeLayer.textures[0].image.data[0], 357.3833923339844); + assert.equal(nodeLayer.textures[0].image.data[0], ELEVATION); done(); }).catch(done); }); - it('get elevation value at with PRECISE_READ_Z', () => { + it('get elevation value at center with PRECISE_READ_Z', () => { const elevation = DEMUtils.getElevationValueAt(viewer.tileLayer, coord, DEMUtils.PRECISE_READ_Z, tiles); - assert.equal(elevation, 369.72571563720703); + assert.equal(elevation, ELEVATION); }); - it('get elevation value at with FAST_READ_Z', () => { + it('get elevation value at center with FAST_READ_Z', () => { const elevation = DEMUtils.getElevationValueAt(viewer.tileLayer, coord, DEMUtils.FAST_READ_Z, tiles); - assert.equal(elevation, 311.4772033691406); + assert.equal(elevation, ELEVATION); }); - it('get terrain at with PRECISE_READ_Z', () => { + it('get terrain at center with PRECISE_READ_Z', () => { const elevation = DEMUtils.getTerrainObjectAt(viewer.tileLayer, coord, DEMUtils.PRECISE_READ_Z, tiles); - assert.equal(elevation.coord.z, 369.72571563720703); + assert.equal(elevation.coord.z, ELEVATION); }); - it('get terrain at with FAST_READ_Z', () => { + it('get terrain at center with FAST_READ_Z', () => { const elevation = DEMUtils.getTerrainObjectAt(viewer.tileLayer, coord, DEMUtils.FAST_READ_Z, tiles); - assert.equal(elevation.coord.z, 311.4772033691406); + assert.equal(elevation.coord.z, ELEVATION); }); }); diff --git a/test/unit/entwine.js b/test/unit/entwine.js index 2de8539ce2..84d7271e3c 100644 --- a/test/unit/entwine.js +++ b/test/unit/entwine.js @@ -1,17 +1,46 @@ import assert from 'assert'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import View from 'Core/View'; import GlobeView from 'Core/Prefab/GlobeView'; import Coordinates from 'Core/Geographic/Coordinates'; import EntwinePointTileSource from 'Source/EntwinePointTileSource'; import EntwinePointTileLayer from 'Layer/EntwinePointTileLayer'; import EntwinePointTileNode from 'Core/EntwinePointTileNode'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +import ept from '../data/entwine/ept.json'; +import eptHierarchy from '../data/entwine/ept-hierarchy/0-0-0-0.json'; + +const baseurl = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds'; +const urlEpt = `${baseurl}/entwine/ept.json`; +const urlEptHierarchy = `${baseurl}/entwine/ept-hierarchy/0-0-0-0.json`; + +const resources = { + [urlEpt]: ept, + [urlEptHierarchy]: eptHierarchy, +}; + describe('Entwine Point Tile', function () { - const source = new EntwinePointTileSource({ - url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/entwine', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, + let source; + let stubFetcherJson; + let stubFetcherArrayBuf; + + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(url => Promise.resolve(JSON.parse(resources[url]))); + stubFetcherArrayBuf = sinon.stub(Fetcher, 'arrayBuffer') + .callsFake(() => Promise.resolve(new ArrayBuffer())); + // currently no test on data fetched... + + source = new EntwinePointTileSource({ + url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/entwine', + }); + }); + + after(function () { + stubFetcherJson.restore(); + stubFetcherArrayBuf.restore(); }); it('loads the EPT structure', (done) => { @@ -79,25 +108,28 @@ describe('Entwine Point Tile', function () { }); describe('Node', function () { - const layer = { source: { url: 'http://server.geo', extension: 'laz' } }; - const root = new EntwinePointTileNode(0, 0, 0, 0, layer, 4000); - root.bbox.setFromArray([1000, 1000, 1000, 0, 0, 0]); - - root.add(new EntwinePointTileNode(1, 0, 0, 0, layer, 3000)); - root.add(new EntwinePointTileNode(1, 0, 0, 1, layer, 3000)); - root.add(new EntwinePointTileNode(1, 0, 1, 1, layer, 3000)); - - root.children[0].add(new EntwinePointTileNode(2, 0, 0, 0, layer, 2000)); - root.children[0].add(new EntwinePointTileNode(2, 0, 1, 0, layer, 2000)); - root.children[1].add(new EntwinePointTileNode(2, 0, 1, 3, layer, 2000)); - root.children[2].add(new EntwinePointTileNode(2, 0, 2, 2, layer, 2000)); - root.children[2].add(new EntwinePointTileNode(2, 0, 3, 3, layer, 2000)); - - root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 0, 0, layer, 1000)); - root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 1, 0, layer, 1000)); - root.children[1].children[0].add(new EntwinePointTileNode(3, 0, 2, 7, layer, 1000)); - root.children[2].children[0].add(new EntwinePointTileNode(3, 0, 5, 4, layer, 1000)); - root.children[2].children[1].add(new EntwinePointTileNode(3, 1, 6, 7, layer)); + let root; + before(function () { + const layer = { source: { url: 'http://server.geo', extension: 'laz' } }; + root = new EntwinePointTileNode(0, 0, 0, 0, layer, 4000); + root.bbox.setFromArray([1000, 1000, 1000, 0, 0, 0]); + + root.add(new EntwinePointTileNode(1, 0, 0, 0, layer, 3000)); + root.add(new EntwinePointTileNode(1, 0, 0, 1, layer, 3000)); + root.add(new EntwinePointTileNode(1, 0, 1, 1, layer, 3000)); + + root.children[0].add(new EntwinePointTileNode(2, 0, 0, 0, layer, 2000)); + root.children[0].add(new EntwinePointTileNode(2, 0, 1, 0, layer, 2000)); + root.children[1].add(new EntwinePointTileNode(2, 0, 1, 3, layer, 2000)); + root.children[2].add(new EntwinePointTileNode(2, 0, 2, 2, layer, 2000)); + root.children[2].add(new EntwinePointTileNode(2, 0, 3, 3, layer, 2000)); + + root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 0, 0, layer, 1000)); + root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 1, 0, layer, 1000)); + root.children[1].children[0].add(new EntwinePointTileNode(3, 0, 2, 7, layer, 1000)); + root.children[2].children[0].add(new EntwinePointTileNode(3, 0, 5, 4, layer, 1000)); + root.children[2].children[1].add(new EntwinePointTileNode(3, 1, 6, 7, layer)); + }); it('finds the common ancestor of two nodes', () => { let ancestor = root.children[2].children[1].children[0].findCommonAncestor(root.children[2].children[0].children[0]); diff --git a/test/unit/feature2mesh.js b/test/unit/feature2mesh.js index cb25096e31..5fb209a512 100644 --- a/test/unit/feature2mesh.js +++ b/test/unit/feature2mesh.js @@ -4,9 +4,9 @@ import assert from 'assert'; import GeoJsonParser from 'Parser/GeoJsonParser'; import Feature2Mesh from 'Converter/Feature2Mesh'; -const geojson = require('../data/geojson/holes.geojson.json'); -const geojson2 = require('../data/geojson/simple.geojson.json'); -const geojson3 = require('../data/geojson/points.geojson.json'); +import geojson from '../data/geojson/holes.geojson'; +import geojson2 from '../data/geojson/simple.geojson'; +import geojson3 from '../data/geojson/points.geojson'; proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); diff --git a/test/unit/featureUtils.js b/test/unit/featureUtils.js index 205c002309..df2d11f475 100644 --- a/test/unit/featureUtils.js +++ b/test/unit/featureUtils.js @@ -4,7 +4,7 @@ import FeaturesUtils from 'Utils/FeaturesUtils'; import Coordinates from 'Core/Geographic/Coordinates'; import { FEATURE_TYPES } from 'Core/Feature'; -const geojson = require('../data/geojson/simple.geojson.json'); +import geojson from '../data/geojson/simple.geojson'; describe('FeaturesUtils', function () { const options = { out: { crs: 'EPSG:4326', buildExtent: true, mergeFeatures: false, structure: '3d' } }; diff --git a/test/unit/featuregeometrylayer.js b/test/unit/featuregeometrylayer.js index 8f1178e1a3..062f1ae9f8 100644 --- a/test/unit/featuregeometrylayer.js +++ b/test/unit/featuregeometrylayer.js @@ -3,71 +3,80 @@ import assert from 'assert'; import GlobeView from 'Core/Prefab/GlobeView'; import FeatureGeometryLayer from 'Layer/FeatureGeometryLayer'; import FileSource from 'Source/FileSource'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import { supportedFetchers } from 'Source/Source'; import Extent from 'Core/Geographic/Extent'; import Coordinates from 'Core/Geographic/Coordinates'; import OBB from 'Renderer/OBB'; import TileMesh from 'Core/TileMesh'; -import Style from 'Core/Style'; +import sinon from 'sinon'; import Renderer from './bootstrap'; +import feature from '../data/filesource/feat_Polygone.geojson'; + describe('Layer with Feature process', function () { const renderer = new Renderer(); - const placement = { coord: new Coordinates('EPSG:4326', 1.5, 43), range: 300000 }; const viewer = new GlobeView(renderer.domElement, placement, { renderer }); - const source = new FileSource({ - url: 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements/09-ariege/departement-09-ariege.geojson', - crs: 'EPSG:4326', - format: 'application/json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }); + let ariege; + let ariegeNoProj4; + let tile; + let context; + let stubSuppFetcher; - const source2 = new FileSource({ - url: 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements/09-ariege/departement-09-ariege.geojson', - crs: 'EPSG:4326', - format: 'application/json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }); + before(function () { + stubSuppFetcher = sinon.stub(supportedFetchers, 'get'); + stubSuppFetcher.withArgs('application/json') + .callsFake(() => () => Promise.resolve(JSON.parse(feature))); + + const source = new FileSource({ + url: 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements/09-ariege/departement-09-ariege.geojson', + crs: 'EPSG:4326', + format: 'application/json', + }); - const ariege = new FeatureGeometryLayer('ariege', { - source, - accurate: true, - style: new Style({ - fill: { - extrusion_height: 5000, - color: new THREE.Color(0xffcc00), + ariege = new FeatureGeometryLayer('ariege', { + source, + accurate: true, + style: { + fill: { + extrusion_height: 5000, + color: new THREE.Color(0xffcc00), + }, }, - }), - zoom: { min: 7 }, - }); + zoom: { min: 7 }, + }); - const ariegeNoProj4 = new FeatureGeometryLayer('ariegeNoProj4', { - source: source2, - accurate: false, - style: new Style({ - fill: { - extrusion_height: 5000, - color: new THREE.Color(0xffcc00), + ariegeNoProj4 = new FeatureGeometryLayer('ariegeNoProj4', { + source, + accurate: false, + style: { + fill: { + extrusion_height: 5000, + color: new THREE.Color(0xffcc00), + }, }, - }), - zoom: { min: 7 }, - }); + zoom: { min: 7 }, + }); - const context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: viewer.mainLoop.scheduler, - geometryLayer: ariege, - view: viewer, - }; + context = { + camera: viewer.camera, + engine: viewer.mainLoop.gfxEngine, + scheduler: viewer.mainLoop.scheduler, + geometryLayer: ariege, + view: viewer, + }; - const extent = new Extent('EPSG:4326', 1.40625, 2.8125, 42.1875, 43.59375); - const geom = new THREE.BufferGeometry(); - geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); - const tile = new TileMesh(geom, new THREE.Material(), viewer.tileLayer, extent, 7); - tile.parent = {}; + const extent = new Extent('EPSG:4326', 1.40625, 2.8125, 42.1875, 43.59375); + const geom = new THREE.BufferGeometry(); + geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); + tile = new TileMesh(geom, new THREE.Material(), viewer.tileLayer, extent, 7); + tile.parent = {}; + }); + + after(function () { + stubSuppFetcher.restore(); + }); it('add layer', function (done) { viewer.addLayer(ariege) @@ -76,6 +85,7 @@ describe('Layer with Feature process', function () { done(); }).catch(done); }); + it('update', function (done) { ariege.whenReady .then(() => { diff --git a/test/unit/featuregeometrylayererror.js b/test/unit/featuregeometrylayererror.js index ecfcc6ff36..09d696d370 100644 --- a/test/unit/featuregeometrylayererror.js +++ b/test/unit/featuregeometrylayererror.js @@ -3,16 +3,15 @@ import assert from 'assert'; import GlobeView from 'Core/Prefab/GlobeView'; import FeatureGeometryLayer from 'Layer/FeatureGeometryLayer'; import FileSource from 'Source/FileSource'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Extent from 'Core/Geographic/Extent'; import Coordinates from 'Core/Geographic/Coordinates'; import OBB from 'Renderer/OBB'; import TileMesh from 'Core/TileMesh'; import Renderer from './bootstrap'; -const geojson_big = require('../data/geojson/map_big.geojson.json'); -const geojson_a = require('../data/geojson/map.geojson.json'); -const geojson_small = require('../data/geojson/map_small.geojson.json'); +import geojson_big from '../data/geojson/map_big.geojson'; +import geojson_a from '../data/geojson/map.geojson'; +import geojson_small from '../data/geojson/map_small.geojson'; const files = [geojson_small, geojson_a, geojson_big]; const errors = [3e-4, 5e-2, 35]; @@ -30,7 +29,6 @@ files.forEach((geojson, i) => { fetchedData: geojson, crs: 'EPSG:4326', format: 'application/json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, }); const source2 = new FileSource(source); diff --git a/test/unit/fetcher.js b/test/unit/fetcher.js new file mode 100644 index 0000000000..29d90de29e --- /dev/null +++ b/test/unit/fetcher.js @@ -0,0 +1,161 @@ +import assert from 'assert'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import Fetcher from 'Provider/Fetcher'; +import { DataTexture, Texture } from 'three'; + +const itownsdataUrl = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master'; + +describe('Fetcher', function () { + const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; + describe('text', function () { + const url = `${itownsdataUrl}/altitude-conversion-grids/EGM2008.gdf`; + it('should load a text file', (done) => { + Fetcher.text(url, networkOptions) + .then((text) => { + const rows = text.split('\n'); + const firstMeasureLine = rows.indexOf(rows.find(row => row.includes('end_of_head'))) + 1; + const rawHeaderData = rows.slice(0, firstMeasureLine); + assert.ok(rows.length > 1); + assert.equal(rawHeaderData.length, firstMeasureLine); + done(); + }) + .catch(done); + }); + it('should fail [test checkResponse()]', (done) => { + const url = `${itownsdataUrl}/noFile.txt`; + let res; + Fetcher.text(url, networkOptions) + .then((text) => { + res = text; + }) + .catch((err) => { + res = err; + }) + .finally(() => { + if (res.response?.status === 404) { + done(); + } else { + done(new Error('response.status !== 404')); + } + }); + }); + }); + + describe('json', function () { + const url = `${itownsdataUrl}/immersive/exampleParis1/cameraCalibration.json`; + it('should load a json file', (done) => { + Fetcher.json(url, networkOptions) + .then((json) => { + assert.ok(Array.isArray(json)); + assert.ok(Object.keys(json[0]).includes('id')); + done(); + }) + .catch(done); + }); + }); + + describe('xml', function () { + const url = `${itownsdataUrl}/ULTRA2009.gpx`; + it('should load a xml file', (done) => { + Fetcher.xml(url, networkOptions) + .then((xml) => { + assert.ok(Object.keys(xml).includes('childNodes')); + assert.ok(xml.childNodes.length > 2); + done(); + }) + .catch(done); + }); + }); + + describe('texture', function () { + // Fetcher.texture always send a texture even with a false url... + const url = 'https://data.geopf.fr/wmts?' + + 'LAYER=ORTHOIMAGERY.ORTHOPHOTOS&FORMAT=image/jpeg&SERVICE=WMTS&VERSION=1.0.0&' + + 'REQUEST=GetTile&STYLE=normal&' + + 'TILEMATRIXSET=PM&TILEMATRIX=2&TILEROW=1&TILECOL=1'; + it('should load a texture', (done) => { + Fetcher.texture(url, networkOptions) + .then((texture) => { + assert.ok(texture instanceof Texture); + assert.ok(texture.isTexture); + done(); + }) + .catch(done); + }); + }); + + describe('arrayBuffer', function () { + const url = `${itownsdataUrl}/altitude-conversion-grids/RAF20_float.gtx`; + it('should load a json file', (done) => { + Fetcher.arrayBuffer(url, networkOptions) + .then((buffer) => { + assert.ok(buffer instanceof ArrayBuffer && buffer.byteLength !== undefined); + done(); + }) + .catch(done); + }); + }); + + describe('textureFloat', function () { + const url = 'https://data.geopf.fr/wmts?' + + 'LAYER=ELEVATION.ELEVATIONGRIDCOVERAGE.SRTM3&FORMAT=image/x-bil;bits=32' + + '&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&' + + 'TILEMATRIXSET=WGS84G&TILEMATRIX=3&TILEROW=2&TILECOL=8'; + it('should get a WebGl2 texture float', (done) => { + Fetcher.textureFloat(url, networkOptions) + .then((texture) => { + assert.ok(texture instanceof DataTexture); + assert.equal(texture.internalFormat, 'R32F'); + assert.equal(texture.version, 1); + done(); + }) + .catch(done); + }).timeout(8000); + it('should get a texture float (without WebGL2)', (done) => { + Fetcher.textureFloat(url, { ...networkOptions, isWebGL2: false }) + .then((texture) => { + assert.ok(texture instanceof DataTexture); + assert.equal(texture.internalFormat, null); + done(); + }) + .catch(done); + }); + }); + + describe('multiple', function () { + const url = `${itownsdataUrl}/vrt/velib-disponibilite-en-temps-reel`; + const extension = { + xml: ['vrt'], + text: ['csv'], + }; + const fileExtensions = new Set(); + Object.values(extension).forEach((extension) => { + fileExtensions.add(...extension); + }); + it('should load a multi file', (done) => { + Fetcher.multiple(url, extension, networkOptions) + .then((res) => { + const fileType = new Set(Object.keys(res)); + assert.ok(fileType.size === fileExtensions.size); + assert.ok([...fileType].every(file => fileExtensions.has(file))); + done(); + }) + .catch(done); + }); + it('should fail (fetchType not valid)', (done) => { + extension.badfetchType = ['badExtension']; + try { + Fetcher.multiple(url, extension, networkOptions) + .then(() => { + const error = new Error('Fetcher.multiple did work'); + done(error); + }); + } catch (err) { + assert.ok(err instanceof Error); + assert.equal(err.message, 'badfetchType is not a valid Fetcher method.'); + done(); + } + }); + }); +}); + diff --git a/test/unit/gdf.js b/test/unit/gdfparser.js similarity index 61% rename from test/unit/gdf.js rename to test/unit/gdfparser.js index 17dfdace22..e7ce813f72 100644 --- a/test/unit/gdf.js +++ b/test/unit/gdfparser.js @@ -1,20 +1,11 @@ import assert from 'assert'; -import { HttpsProxyAgent } from 'https-proxy-agent'; -import Fetcher from 'Provider/Fetcher'; +import fs from 'fs'; import GDFParser from 'Parser/GDFParser'; +const gdfFile = fs.readFileSync('./test/data/EGM2008_simplified.gdf', { encoding: 'utf8' }); describe('GDFParser', function () { - let text; - - before(async () => { - const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; - text = await Fetcher.text( - 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/altitude-conversion-grids/' + - 'EGM2008.gdf', - networkOptions, - ); - }); + const text = gdfFile; it('should default `options.in.crs` parameter to `EPSG:4326`', async function () { const geoidGrid = await GDFParser.parse(text); @@ -27,12 +18,12 @@ describe('GDFParser', function () { assert.strictEqual(geoidGrid.extent.east, 180); assert.strictEqual(geoidGrid.extent.south, -90); assert.strictEqual(geoidGrid.extent.north, 90); - assert.strictEqual(geoidGrid.step.x, 1); - assert.strictEqual(geoidGrid.step.y, 1); + assert.strictEqual(geoidGrid.step.x, 45); + assert.strictEqual(geoidGrid.step.y, 45); }); it('should set a correct data reading method for `GeoidGrid`', async function () { const geoidGrid = await GDFParser.parse(text, { in: { crs: 'EPSG:4326' } }); - assert.strictEqual(geoidGrid.getData(geoidGrid.dataSize.y - 2, 1), 13.813008707225); + assert.strictEqual(geoidGrid.getData(geoidGrid.dataSize.y - 2, 1), -26.975163013039); }); }); diff --git a/test/unit/geoidlayer.js b/test/unit/geoidlayer.js index b4f7d51d96..ad76fca779 100644 --- a/test/unit/geoidlayer.js +++ b/test/unit/geoidlayer.js @@ -1,44 +1,64 @@ +/* global createGtxBuffer */ import * as THREE from 'three'; import assert from 'assert'; import GeoidLayer from 'Layer/GeoidLayer'; import FileSource from 'Source/FileSource'; +import { supportedFetchers } from 'Source/Source'; import Coordinates from 'Core/Geographic/Coordinates'; import GlobeView from 'Core/Prefab/GlobeView'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Extent from 'Core/Geographic/Extent'; import OBB from 'Renderer/OBB'; import TileMesh from 'Core/TileMesh'; +import sinon from 'sinon'; import Renderer from './bootstrap'; +const ELEVATION = 10; + describe('GlobeView', function () { const renderer = new Renderer(); const placement = { coord: new Coordinates('EPSG:4326', 4.631512, 43.675626), range: 3919 }; const view = new GlobeView(renderer.domElement, placement, { renderer }); - const url = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/altitude-conversion-grids/RAF20_float.gtx'; + let geoidLayer; + let context; + let tile; + let stubSuppFetcher; - const geoidSource = new FileSource({ - url, - crs: 'EPSG:4326', - format: 'application/gtx', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }); - // Specify the type geoid height data are encoded with. See GTXParser documentation at - // http://www.itowns-project.org/itowns/docs/#api/Parser/GTXParser for more. - geoidSource.dataType = 'float'; + before(function () { + const buffer = createGtxBuffer(ELEVATION); + stubSuppFetcher = sinon.stub(supportedFetchers, 'get'); + stubSuppFetcher.withArgs('application/gtx') + .callsFake(() => () => Promise.resolve(buffer)); - // Create a Layer to support geoid height data and add it to the view. - const geoidLayer = new GeoidLayer('geoid', { - source: geoidSource, - }); + const url = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + + 'altitude-conversion-grids/RAF20_float.gtx'; + + const geoidSource = new FileSource({ + url, + crs: 'EPSG:4326', + format: 'application/gtx', + }); + // Specify the type geoid height data are encoded with. See GTXParser documentation at + // http://www.itowns-project.org/itowns/docs/#api/Parser/GTXParser for more. + geoidSource.dataType = 'float'; - const context = {}; + // Create a Layer to support geoid height data and add it to the view. + geoidLayer = new GeoidLayer('geoid', { + source: geoidSource, + }); - const extent = new Extent('EPSG:4326', 4.1, 4.3, 48.1, 48.3); - const geom = new THREE.BufferGeometry(); - geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); - const tile = new TileMesh(geom, new THREE.Material(), view.tileLayer, extent, 9); - tile.parent = {}; + context = {}; + + const extent = new Extent('EPSG:4326', 4.1, 4.3, 48.1, 48.3); + const geom = new THREE.BufferGeometry(); + geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); + tile = new TileMesh(geom, new THREE.Material(), view.tileLayer, extent, 9); + tile.parent = {}; + }); + + after(function () { + stubSuppFetcher.restore(); + }); it('add geoid layer', function (done) { view.addLayer(geoidLayer) @@ -52,9 +72,10 @@ describe('GlobeView', function () { .then(() => { geoidLayer.update(context, geoidLayer, tile, {}) .then(() => { - assert.equal(tile.geoidHeight, 45.72800064087844); + assert.equal(tile.geoidHeight, 10); done(); - }); + }) + .catch(done); }).catch(done); }); }); diff --git a/test/unit/geojson.js b/test/unit/geojson.js index 107df45833..2116eebade 100644 --- a/test/unit/geojson.js +++ b/test/unit/geojson.js @@ -3,9 +3,9 @@ import assert from 'assert'; import GeoJsonParser from 'Parser/GeoJsonParser'; import Extent from 'Core/Geographic/Extent'; -const holes = require('../data/geojson/holes.geojson.json'); -const gpx = require('../data/geojson/gpx.geojson.json'); -const points = require('../data/geojson/points.geojson.json'); +import holes from '../data/geojson/holes.geojson'; +import gpx from '../data/geojson/gpx.geojson'; +import points from '../data/geojson/points.geojson'; proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); diff --git a/test/unit/gltfLoader.js b/test/unit/gltfLoader.js index cbf1f423fc..94b79c8965 100644 --- a/test/unit/gltfLoader.js +++ b/test/unit/gltfLoader.js @@ -1,6 +1,8 @@ import assert from 'assert'; +import fs from 'fs'; import { glTFLoader } from 'Parser/B3dmParser'; -import gltf from '../data/gltf/Box.gltf'; + +const gltf = fs.readFileSync('./test/data/gltf/Box.gltf'); if (typeof atob === 'undefined') { global.atob = b64Encoded => Buffer.from(b64Encoded, 'base64').toString('binary'); diff --git a/test/unit/gtx.js b/test/unit/gtxparser.js similarity index 82% rename from test/unit/gtx.js rename to test/unit/gtxparser.js index 3905422d92..56eab39505 100644 --- a/test/unit/gtx.js +++ b/test/unit/gtxparser.js @@ -1,20 +1,9 @@ +/* global createGtxBuffer */ import assert from 'assert'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import GTXParser from 'Parser/GTXParser'; -import Fetcher from 'Provider/Fetcher'; - describe('GTXParser', function () { - let buffer; - - before(async () => { - const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; - buffer = await Fetcher.arrayBuffer( - 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/altitude-conversion-grids/' + - 'RAF20_float.gtx', - networkOptions, - ); - }); + const buffer = createGtxBuffer(); it('should throw error if dataType parameter is wrongly specified', async function () { assert.throws( diff --git a/test/unit/isg.js b/test/unit/isg.js deleted file mode 100644 index ed57ea88bc..0000000000 --- a/test/unit/isg.js +++ /dev/null @@ -1,38 +0,0 @@ -import assert from 'assert'; -import { HttpsProxyAgent } from 'https-proxy-agent'; -import Fetcher from 'Provider/Fetcher'; -import ISGParser from 'Parser/ISGParser'; - - -describe('ISGParser', function () { - let text; - - before(async () => { - const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; - text = await Fetcher.text( - 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/altitude-conversion-grids/' + - 'raf09.isg', - networkOptions, - ); - }); - - it('should default `options.in.crs` parameter to `EPSG:4326`', async function () { - const geoidGrid = await ISGParser.parse(text); - assert.strictEqual(geoidGrid.extent.crs, 'EPSG:4326'); - }); - - it('should parse text data into a GeoidGrid', async function () { - const geoidGrid = await ISGParser.parse(text, { in: { crs: 'EPSG:4326' } }); - assert.strictEqual(geoidGrid.extent.west, -5.50005); - assert.strictEqual(geoidGrid.extent.east, 8.50005); - assert.strictEqual(geoidGrid.extent.south, 42.0); - assert.strictEqual(geoidGrid.extent.north, 51.5); - assert.strictEqual(geoidGrid.step.x, 0.0333); - assert.strictEqual(geoidGrid.step.y, 0.025); - }); - - it('should set a correct data reading method for `GeoidGrid`', async function () { - const geoidGrid = await ISGParser.parse(text, { in: { crs: 'EPSG:4326' } }); - assert.strictEqual(geoidGrid.getData(1, 1), 53.47); - }); -}); diff --git a/test/unit/isgparser.js b/test/unit/isgparser.js new file mode 100644 index 0000000000..e2bac15e50 --- /dev/null +++ b/test/unit/isgparser.js @@ -0,0 +1,29 @@ +import assert from 'assert'; +import fs from 'fs'; +import ISGParser from 'Parser/ISGParser'; + +const isgFile = fs.readFileSync('./test/data/raf09_simplified.isg', { encoding: 'utf8' }); + +describe('ISGParser', function () { + const text = isgFile; + + it('should default `options.in.crs` parameter to `EPSG:4326`', async function () { + const geoidGrid = await ISGParser.parse(text); + assert.strictEqual(geoidGrid.extent.crs, 'EPSG:4326'); + }); + + it('should parse text data into a GeoidGrid', async function () { + const geoidGrid = await ISGParser.parse(text, { in: { crs: 'EPSG:4326' } }); + assert.strictEqual(geoidGrid.extent.west, -5.50460); + assert.strictEqual(geoidGrid.extent.east, 8.50460); + assert.strictEqual(geoidGrid.extent.south, 42.94); + assert.strictEqual(geoidGrid.extent.north, 50.56); + assert.strictEqual(geoidGrid.step.x, 0.0242); + assert.strictEqual(geoidGrid.step.y, 1.9050); + }); + + it('should set a correct data reading method for `GeoidGrid`', async function () { + const geoidGrid = await ISGParser.parse(text, { in: { crs: 'EPSG:4326' } }); + assert.strictEqual(geoidGrid.getData(1, 1), 60); + }); +}); diff --git a/test/unit/label.js b/test/unit/label.js index a0c5c7db6d..b0fb2a4f61 100644 --- a/test/unit/label.js +++ b/test/unit/label.js @@ -9,10 +9,9 @@ import LabelLayer from 'Layer/LabelLayer'; import GlobeView from 'Core/Prefab/GlobeView'; import ColorLayer from 'Layer/ColorLayer'; import FileSource from 'Source/FileSource'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Renderer from './bootstrap'; -const geojson = require('../data/geojson/simple.geojson.json'); +import geojson from '../data/geojson/simple.geojson'; describe('LabelLayer', function () { let layer; @@ -146,8 +145,6 @@ describe('Label2DRenderer', function () { fetchedData: geojson, crs: 'EPSG:4326', format: 'application/json', - // TODO : is it necessary since we use fetchedData property ? - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, }); const gpxStyle = new Style({ text: { field: '{name}' } }); diff --git a/test/unit/lasparser.js b/test/unit/lasparser.js index a52332fd98..0f5d5687a5 100644 --- a/test/unit/lasparser.js +++ b/test/unit/lasparser.js @@ -4,67 +4,57 @@ import LASParser from 'Parser/LASParser'; import Fetcher from 'Provider/Fetcher'; import { compareWithEpsilon } from './utils'; +const baseurl = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds'; +const lasUrl = `${baseurl}/data_test.las`; + +const url = 'https://github.com/connormanning/copc.js/raw/master/src/test/data'; +const lazV14Url = `${url}/ellipsoid-1.4.laz`; + describe('LASParser', function () { let lasData; - let lazData; - let lazDataV1_4; - - before(async () => { + let lazV14Data; + it('fetch binaries', async function () { const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; - const baseurl = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/'; - lazData = await Fetcher.arrayBuffer(`${baseurl}data_test.laz`, networkOptions); - lasData = await Fetcher.arrayBuffer(`${baseurl}data_test.las`, networkOptions); - lazDataV1_4 = await Fetcher.arrayBuffer(`${baseurl}ellipsoid-1.4.laz`, networkOptions); - LASParser.enableLazPerf('./examples/libs/laz-perf'); - }); - - it('parses a las file to a THREE.BufferGeometry', async () => { - const bufferGeometry = await LASParser.parse(lasData); - assert.strictEqual(bufferGeometry.userData.pointCount, 106); - assert.strictEqual(bufferGeometry.attributes.position.count, bufferGeometry.userData.pointCount); - assert.strictEqual(bufferGeometry.attributes.intensity.count, bufferGeometry.userData.pointCount); - assert.strictEqual(bufferGeometry.attributes.classification.count, bufferGeometry.userData.pointCount); - assert.strictEqual(bufferGeometry.attributes.color, undefined); + lasData = await Fetcher.arrayBuffer(lasUrl, networkOptions); + lazV14Data = await Fetcher.arrayBuffer(lazV14Url, networkOptions); + }).timeout(4000); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.min[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.min[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.min[2], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.max[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.max[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.max[2], 0.1)); - }); + describe('unit tests', function () { + const epsilon = 0.1; + LASParser.enableLazPerf('./examples/libs/laz-perf'); - describe('parses a laz file to a THREE.BufferGeometry', function () { - it('laz v1.2', async () => { - const bufferGeometry = await LASParser.parse(lazData); - assert.strictEqual(bufferGeometry.userData.pointCount, 57084); + it('parses a las file to a THREE.BufferGeometry', async function () { + if (!lasData) { this.skip(); } + const bufferGeometry = await LASParser.parse(lasData); + assert.strictEqual(bufferGeometry.userData.pointCount, 106); assert.strictEqual(bufferGeometry.attributes.position.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.intensity.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.classification.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.color, undefined); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.min[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.min[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.min[2], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.max[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.max[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.max[2], 0.1)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.min[0], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.min[1], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.min[2], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.max[0], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.max[1], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.max[2], epsilon)); }); - it('laz v1.4', async () => { - const bufferGeometry = await LASParser.parse(lazDataV1_4); + it('parses a laz file to a THREE.BufferGeometry', async function () { + if (!lazV14Data) { this.skip(); } + const bufferGeometry = await LASParser.parse(lazV14Data); assert.strictEqual(bufferGeometry.userData.pointCount, 100000); assert.strictEqual(bufferGeometry.attributes.position.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.intensity.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.classification.count, bufferGeometry.userData.pointCount); assert.strictEqual(bufferGeometry.attributes.color.count, bufferGeometry.userData.pointCount); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.min[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.min[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.min[2], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.max[0], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.max[1], 0.1)); - assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.max[2], 0.1)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.min[0], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.min[1], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.min.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.min[2], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.x + bufferGeometry.userData.origin.x, bufferGeometry.userData.max[0], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.y + bufferGeometry.userData.origin.y, bufferGeometry.userData.max[1], epsilon)); + assert.ok(compareWithEpsilon(bufferGeometry.boundingBox.max.z + bufferGeometry.userData.origin.z, bufferGeometry.userData.max[2], epsilon)); }); }); }); diff --git a/test/unit/layeredmaterial.js b/test/unit/layeredmaterial.js index ca7407c0ed..77497b3389 100644 --- a/test/unit/layeredmaterial.js +++ b/test/unit/layeredmaterial.js @@ -9,26 +9,45 @@ import * as THREE from 'three'; import Extent from 'Core/Geographic/Extent'; import OBB from 'Renderer/OBB'; import LayeredMaterial from 'Renderer/LayeredMaterial'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; describe('material state vs layer state', function () { const renderer = new Renderer(); const p = { coord: new Coordinates('EPSG:4326', -75.6114, 40.03428, 0), heading: 180, range: 4000, tilt: 22 }; - const view = new GlobeView(renderer.domElement, p, { renderer, noControls: true }); - const layer = new ColorLayer('color', { crs: 'EPSG:4326', source: new TMSSource({ crs: 'EPSG:4326', url: 'url' }) }); - view.tileLayer.colorLayersOrder = [layer.id]; - view.addLayer(layer); + let layer; + let material; + let node; + let context; + let stubFetcherTexture; + + before(function () { + stubFetcherTexture = sinon.stub(Fetcher, 'texture') + .callsFake(() => Promise.resolve(null)); + const source = new TMSSource({ crs: 'EPSG:4326', url: 'nullUrl' }); + layer = new ColorLayer('color', { + source, + crs: 'EPSG:4326', + }); + view.tileLayer.colorLayersOrder = [layer.id]; + view.addLayer(layer); - const extent = new Extent('TMS:4326', 3, 0, 0).as('EPSG:4326'); - const material = new LayeredMaterial(); - const geom = new THREE.BufferGeometry(); - geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); - const node = new TileMesh(geom, material, view.tileLayer, extent); - node.parent = { }; + const extent = new Extent('TMS:4326', 3, 0, 0).as('EPSG:4326'); + material = new LayeredMaterial(); + const geom = new THREE.BufferGeometry(); + geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); + node = new TileMesh(geom, material, view.tileLayer, extent); + node.parent = { }; - const context = { view, scheduler: view.mainLoop.scheduler }; + context = { view, scheduler: view.mainLoop.scheduler }; + }); + + after(function () { + stubFetcherTexture.restore(); + }); it('should correctly initialize opacity & visibility', () => { updateLayeredMaterialNodeImagery(context, layer, node, node.parent); @@ -37,6 +56,7 @@ describe('material state vs layer state', function () { assert.equal(nodeLayer.opacity, layer.opacity); assert.equal(nodeLayer.visible, layer.visible); }); + it('should update material opacity & visibility', () => { layer.opacity = 0.5; layer.visible = false; @@ -44,6 +64,7 @@ describe('material state vs layer state', function () { assert.equal(nodeLayer.opacity, layer.opacity); assert.equal(nodeLayer.visible, layer.visible); }); + it('should update material uniforms', () => { layer.visible = false; node.onBeforeRender(); diff --git a/test/unit/orientedimagelayer.js b/test/unit/orientedimagelayer.js index 7eeb21460c..27b81656f5 100644 --- a/test/unit/orientedimagelayer.js +++ b/test/unit/orientedimagelayer.js @@ -2,69 +2,97 @@ import proj4 from 'proj4'; import assert from 'assert'; import OrientedImageLayer from 'Layer/OrientedImageLayer'; import OrientedImageSource from 'Source/OrientedImageSource'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Coordinates from 'Core/Geographic/Coordinates'; import GlobeView from 'Core/Prefab/GlobeView'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +import panoData from '../data/OrientedImage/panoramicsMetaDataParis.geojson'; +import camCalibration from '../data/OrientedImage/cameraCalibration.json'; + +const baseurl = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/immersive'; +const orientationsUrl = `${baseurl}/exampleParis1/panoramicsMetaDataParis.geojson`; +const calibrationUrl = `${baseurl}/exampleParis1/cameraCalibration.json`; + +const resources = { + [orientationsUrl]: panoData, + [calibrationUrl]: camCalibration, +}; + describe('Oriented Image Layer', function () { - proj4.defs('EPSG:2154', '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); - const renderer = new Renderer(); + let orImgLayer; + let context; + let stubFetcherJson; + + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(url => Promise.resolve(JSON.parse(resources[url]))); + proj4.defs('EPSG:2154', '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); + const renderer = new Renderer(); + + const p = { + coord: new Coordinates('EPSG:4326', 2.33481381, 48.85060296), + range: 25, + }; + const viewer = new GlobeView(renderer.domElement, p, + { renderer, + noControls: true, + handleCollision: false, + sseSubdivisionThreshold: 10, + }); - const p = { - coord: new Coordinates('EPSG:4326', 2.33481381, 48.85060296), - range: 25, - }; - const viewer = new GlobeView(renderer.domElement, p, - { renderer, - noControls: true, - handleCollision: false, - sseSubdivisionThreshold: 10, + // Prepare oriented image source + const orientedImageSource = new OrientedImageSource({ + url: 'http://www.itowns-project.org/itowns-sample-data-small/images/140616/Paris-140616_0740-{cameraId}-00001_0000{panoId}.jpg', + // Url to a GEOJSON file describing feature points. It describes position and orientation of each panoramic. + orientationsUrl: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + + 'immersive/exampleParis1/panoramicsMetaDataParis.geojson', + // Url of a a JSON file with calibration for all cameras. see [CameraCalibrationParser]{@link module:CameraCalibrationParser.parse} + // in this example, we have the ladybug, it's a set of 6 cameras + calibrationUrl: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + + 'immersive/exampleParis1/cameraCalibration.json', }); - // Prepare oriented image source - const orientedImageSource = new OrientedImageSource({ - url: 'http://www.itowns-project.org/itowns-sample-data-small/images/140616/Paris-140616_0740-{cameraId}-00001_0000{panoId}.jpg', - // Url to a GEOJSON file describing feature points. It describre position and orientation of each panoramic. - orientationsUrl: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/immersive/exampleParis1/panoramicsMetaDataParis.geojson', - // Url of a a JSON file with calibration for all cameras. see [CameraCalibrationParser]{@link module:CameraCalibrationParser.parse} - // in this example, we have the ladybug, it's a set of 6 cameras - calibrationUrl: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/immersive/exampleParis1/cameraCalibration.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }); + // Create oriented image layer + orImgLayer = new OrientedImageLayer('demo_orientedImage', { + // Radius in meter of the sphere used as a background. + backgroundDistance: 1200, + source: orientedImageSource, + crs: viewer.referenceCrs, + useMask: false, + }); - // Create oriented image layer - const olayer = new OrientedImageLayer('demo_orientedImage', { - // Radius in meter of the sphere used as a background. - backgroundDistance: 1200, - source: orientedImageSource, - crs: viewer.referenceCrs, - useMask: false, - }); + viewer.addLayer(orImgLayer, viewer.tileLayer); - viewer.addLayer(olayer, viewer.tileLayer); + context = { + camera: viewer.camera, + engine: viewer.mainLoop.gfxEngine, + scheduler: viewer.mainLoop.scheduler, + view: viewer, + }; + }); - const context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: viewer.mainLoop.scheduler, - view: viewer, - }; + after(function () { + stubFetcherJson.restore(); + }); it('Add oriented image layer', function (done) { - olayer.whenReady + orImgLayer.whenReady .then(() => { - assert.equal(olayer.cameras.length, 5); + assert.equal(orImgLayer.cameras.length, 1); + assert.equal(orImgLayer.cameras[0].name, 300); + assert.equal(orImgLayer.material.cameras[0].name, 300); done(); }).catch(done); }); it('PreUpdate oriented image layer', function (done) { - olayer.whenReady + orImgLayer.whenReady .then(() => { - assert.equal(olayer.currentPano, undefined); - olayer.preUpdate(context); - assert.equal(olayer.currentPano.id, 482); + assert.equal(orImgLayer.currentPano, undefined); + orImgLayer.preUpdate(context); + assert.equal(orImgLayer.currentPano.id, 482); done(); }).catch(done); }); diff --git a/test/unit/potree.js b/test/unit/potree.js index 27a27c714b..9b88f17ddc 100644 --- a/test/unit/potree.js +++ b/test/unit/potree.js @@ -1,128 +1,174 @@ import assert from 'assert'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import PotreeLayer from 'Layer/PotreeLayer'; import PotreeSource from 'Source/PotreeSource'; import View from 'Core/View'; import GlobeView from 'Core/Prefab/GlobeView'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import Coordinates from 'Core/Geographic/Coordinates'; import PotreeNode from 'Core/PotreeNode'; import PointsMaterial from 'Renderer/PointsMaterial'; import OrientedImageMaterial from 'Renderer/OrientedImageMaterial'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +const resources = new Map(); + +// potree 1.7 +const baseurl = 'https://raw.githubusercontent.com/potree/potree/develop/pointclouds/lion_takanawa/'; +const fileName = 'cloud.js'; + describe('Potree', function () { - const placement = { coord: new Coordinates('EPSG:4326', 4.631512, 43.675626), range: 250 }; - let renderer; - let viewer; - let potreeLayer; - let context; - let elt; - - before(function () { - renderer = new Renderer(); - viewer = new GlobeView(renderer.domElement, placement, { renderer }); - - // Configure Point Cloud layer - potreeLayer = new PotreeLayer('eglise_saint_blaise_arles', { - source: new PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/gmaillet/dataset/master/', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }), - onPointsCreated: () => {}, - crs: viewer.referenceCrs, - }); + let metadataJson; + let potreeRRhrc; + let potreeRRbin; + let potreeRR0bin; + let dataFetched; + it('fetch test data from https://github.com/potree', async function () { + const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; + metadataJson = await Fetcher.json(`${baseurl}/${fileName}`, networkOptions); + potreeRRbin = await Fetcher.arrayBuffer(`${baseurl}/data/r/r.bin`, networkOptions); + potreeRRhrc = await Fetcher.arrayBuffer(`${baseurl}/data/r/r.hrc`, networkOptions); + potreeRR0bin = await Fetcher.arrayBuffer(`${baseurl}/data/r/r0.bin`, networkOptions); - context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: viewer.mainLoop.scheduler, - geometryLayer: potreeLayer, - view: viewer, - }; - }); + resources.set(`${baseurl}/data/r/r.hrc`, potreeRRhrc); + resources.set(`${baseurl}/data/r/r.bin`, potreeRRbin); + resources.set(`${baseurl}/data/r/r0.bin`, potreeRR0bin); - it('Add point potree layer', function (done) { - View.prototype.addLayer.call(viewer, potreeLayer) - .then((layer) => { - context.camera.camera3D.updateMatrixWorld(); - assert.equal(layer.root.children.length, 7); - layer.bboxes.visible = true; - done(); - }).catch(done); - }); + dataFetched = true; + }).timeout(5000); - it('preupdate potree layer', function () { - elt = potreeLayer.preUpdate(context, new Set([potreeLayer])); - assert.equal(elt.length, 1); - }); + describe('unit tests', function () { + const placement = { coord: new Coordinates('EPSG:4326', 4.631512, 43.675626), range: 250 }; + let renderer; + let viewer; + let potreeLayer; + let context; + let elt; + let stubFetcherJson; + let stubFetcherArrayBuf; - it('update potree layer', function (done) { - assert.equal(potreeLayer.group.children.length, 0); - potreeLayer.update(context, potreeLayer, elt[0]); - elt[0].promise - .then(() => { - assert.equal(potreeLayer.group.children.length, 1); - done(); - }).catch(done); - }); + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(() => Promise.resolve(metadataJson)); + stubFetcherArrayBuf = sinon.stub(Fetcher, 'arrayBuffer') + .callsFake(url => Promise.resolve(resources.get(url))); - it('postUpdate potree layer', function () { - potreeLayer.postUpdate(context, potreeLayer); - }); + if (!dataFetched) { this.skip(); } + + renderer = new Renderer(); + viewer = new GlobeView(renderer.domElement, placement, { renderer }); + + const source = new PotreeSource({ + file: fileName, + url: baseurl, + }); - describe('potree Node', function () { - const numPoints = 1000; - const childrenBitField = 5; + // Configure Point Cloud layer + potreeLayer = new PotreeLayer('lion_takanawa', { + source, + onPointsCreated: () => {}, + crs: viewer.referenceCrs, + }); - it('instance', function (done) { - const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); - assert.equal(root.numPoints, numPoints); - assert.equal(root.childrenBitField, childrenBitField); - done(); + context = { + camera: viewer.camera, + engine: viewer.mainLoop.gfxEngine, + scheduler: viewer.mainLoop.scheduler, + geometryLayer: potreeLayer, + view: viewer, + }; }); - it('load octree', function (done) { - const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); - root.loadOctree() - .then(() => { - assert.equal(7, root.children.length); - done(); - }).catch(done); + after(function () { + stubFetcherJson.restore(); + stubFetcherArrayBuf.restore(); }); - it('load child node', function (done) { - const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); - root.loadOctree() - .then(() => root.children[0].load() + describe('potree Layer', function () { + it('Add point potree layer', function (done) { + View.prototype.addLayer.call(viewer, potreeLayer) + .then((layer) => { + context.camera.camera3D.updateMatrixWorld(); + assert.equal(layer.root.children.length, 6); + layer.bboxes.visible = true; + done(); + }).catch(done); + }); + + it('preupdate potree layer', function () { + elt = potreeLayer.preUpdate(context, new Set([potreeLayer])); + assert.equal(elt.length, 1); + }); + + it('update potree layer', function (done) { + assert.equal(potreeLayer.group.children.length, 0); + potreeLayer.update(context, potreeLayer, elt[0]); + elt[0].promise .then(() => { - assert.equal(2, root.children[0].children.length); + assert.equal(potreeLayer.group.children.length, 1); done(); - }), - ).catch(done); - }); - }); + }).catch(done); + }); - describe('Point Material and oriented images', () => { - const orientedImageMaterial = new OrientedImageMaterial([]); - const pMaterial = new PointsMaterial({ orientedImageMaterial }); - const pMaterial2 = new PointsMaterial(); - it('instance', () => { - assert.ok(pMaterial); - }); - it('Define isWebGL2 on before compile', () => { - const shader = {}; - pMaterial.onBeforeCompile(shader, renderer); - assert.equal(shader.glslVersion, '300 es'); + it('postUpdate potree layer', function () { + potreeLayer.postUpdate(context, potreeLayer); + }); }); - it('copy', () => { - pMaterial2.copy(pMaterial); - assert.equal(pMaterial2.uniforms.projectiveTextureAlphaBorder.value, 20); + + describe('potree Node', function () { + const numPoints = 1000; + const childrenBitField = 5; + + it('instance', function (done) { + const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); + assert.equal(root.numPoints, numPoints); + assert.equal(root.childrenBitField, childrenBitField); + done(); + }); + + it('load octree', function (done) { + const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); + root.loadOctree() + .then(() => { + assert.equal(6, root.children.length); + done(); + }).catch(done); + }); + + it('load child node', function (done) { + const root = new PotreeNode(numPoints, childrenBitField, potreeLayer); + root.loadOctree() + .then(() => root.children[0].load() + .then(() => { + assert.equal(8, root.children[0].children.length); + done(); + }), + ).catch(done); + }); }); - it('update', () => { - pMaterial.visible = false; - pMaterial2.update(pMaterial); - assert.equal(pMaterial2.visible, false); + + describe('Point Material and oriented images', () => { + const orientedImageMaterial = new OrientedImageMaterial([]); + const pMaterial = new PointsMaterial({ orientedImageMaterial }); + const pMaterial2 = new PointsMaterial(); + it('instance', () => { + assert.ok(pMaterial); + }); + it('Define isWebGL2 on before compile', () => { + const shader = {}; + pMaterial.onBeforeCompile(shader, renderer); + assert.equal(shader.glslVersion, '300 es'); + }); + it('copy', () => { + pMaterial2.copy(pMaterial); + assert.equal(pMaterial2.uniforms.projectiveTextureAlphaBorder.value, 20); + }); + it('update', () => { + pMaterial.visible = false; + pMaterial2.update(pMaterial); + assert.equal(pMaterial2.visible, false); + }); }); }); }); diff --git a/test/unit/potreelayerparsing.js b/test/unit/potreelayerparsing.js index e752effeeb..0f1207edb2 100644 --- a/test/unit/potreelayerparsing.js +++ b/test/unit/potreelayerparsing.js @@ -1,114 +1,145 @@ import assert from 'assert'; +import { HttpsProxyAgent } from 'https-proxy-agent'; import PotreeLayer from 'Layer/PotreeLayer'; import PotreeSource from 'Source/PotreeSource'; import Coordinates from 'Core/Geographic/Coordinates'; import GlobeView from 'Core/Prefab/GlobeView'; import View from 'Core/View'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; +const baseurl = 'https://raw.githubusercontent.com/potree/potree/develop/pointclouds/lion_takanawa/'; +const fileName = 'cloud.js'; + describe('Potree Provider', function () { - const renderer = new Renderer(); - const placement = { coord: new Coordinates('EPSG:4326', 1.5, 43), range: 300000 }; - const view = new GlobeView(renderer.domElement, placement, { renderer }); - - it('should correctly parse normal information in cloud', function (done) { - // No normals - const cloud = { - boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, - scale: 1.0, - pointAttributes: ['POSITION', 'RGB'], - octreeDir: 'eglise_saint_blaise_arles', - }; + let potreeRRhrc; + it('fetch test data from https://github.com/potree', async function () { + const networkOptions = process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}; + potreeRRhrc = await Fetcher.arrayBuffer(`${baseurl}/data/r/r.hrc`, networkOptions); + }); + describe('unit tests', function () { + const placement = { coord: new Coordinates('EPSG:4326', 1.5, 43), range: 300000 }; - const layers = []; - let source = new PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/gmaillet/dataset/master/', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - cloud, - }); + let renderer; + let view; + let stubFetcherArrayBuf; - const layer1 = new PotreeLayer('pointsCloud1', { source, crs: view.referenceCrs }); - layers.push(layer1); - const p1 = layer1.whenReady.then((l) => { - const normalDefined = l.material.defines.NORMAL || l.material.defines.NORMAL_SPHEREMAPPED || l.material.defines.NORMAL_OCT16; - assert.ok(!normalDefined); - }); + before(function () { + stubFetcherArrayBuf = sinon.stub(Fetcher, 'arrayBuffer') + .callsFake(() => Promise.resolve(potreeRRhrc)); - // // // normals as vector - source = new PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/gmaillet/dataset/master/', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - cloud: { - boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, - scale: 1.0, - pointAttributes: ['POSITION', 'NORMAL', 'CLASSIFICATION'], - octreeDir: 'eglise_saint_blaise_arles', - }, - }); + if (!potreeRRhrc) { this.skip(); } - const layer2 = new PotreeLayer('pointsCloud2', { source, crs: view.referenceCrs }); - layers.push(layer2); - const p2 = layer2.whenReady.then((l) => { - assert.ok(l.material.defines.NORMAL); - assert.ok(!l.material.defines.NORMAL_SPHEREMAPPED); - assert.ok(!l.material.defines.NORMAL_OCT16); + renderer = new Renderer(); + view = new GlobeView(renderer.domElement, placement, { renderer }); }); - - // // spheremapped normals - source = new PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/gmaillet/dataset/master/', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - cloud: { - boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, - scale: 1.0, - pointAttributes: ['POSITION', 'COLOR_PACKED', 'NORMAL_SPHEREMAPPED'], - octreeDir: 'eglise_saint_blaise_arles', - }, + after(function () { + stubFetcherArrayBuf.restore(); }); - const layer3 = new PotreeLayer('pointsCloud3', { source, crs: view.referenceCrs }); - layers.push(layer3); - const p3 = layer3.whenReady.then((l) => { - assert.ok(!l.material.defines.NORMAL); - assert.ok(l.material.defines.NORMAL_SPHEREMAPPED); - assert.ok(!l.material.defines.NORMAL_OCT16); - }); + describe('cloud information parsing', function _() { + it('cloud with no normal information', function _it(done) { + // No normals + const cloud = { + boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, + scale: 1.0, + pointAttributes: ['POSITION', 'RGB'], + octreeDir: 'data', + }; - // // oct16 normals - source = new PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/gmaillet/dataset/master/', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - cloud: { - boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, - scale: 1.0, - pointAttributes: ['POSITION', 'COLOR_PACKED', 'CLASSIFICATION', 'NORMAL_OCT16'], - octreeDir: 'eglise_saint_blaise_arles', - }, - }); - const layer4 = new PotreeLayer('pointsCloud4', { source, crs: view.referenceCrs }); - - layers.push(layer4); - const p4 = layer4.whenReady - .then((l) => { - assert.ok(!l.material.defines.NORMAL); - assert.ok(!l.material.defines.NORMAL_SPHEREMAPPED); - assert.ok(l.material.defines.NORMAL_OCT16); + const source = new PotreeSource({ + file: fileName, + url: baseurl, + cloud, + }); + + const layer = new PotreeLayer('pointsCloudNoNormal', { source, crs: view.referenceCrs }); + View.prototype.addLayer.call(view, layer); + layer.whenReady.then((l) => { + const normalDefined = l.material.defines.NORMAL || l.material.defines.NORMAL_SPHEREMAPPED || l.material.defines.NORMAL_OCT16; + assert.ok(!normalDefined); + done(); + }).catch(done); + }); + + it('cloud with normals as vector', function _it(done) { + // // // // normals as vector + const cloud = { + boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, + scale: 1.0, + pointAttributes: ['POSITION', 'NORMAL', 'CLASSIFICATION'], + octreeDir: 'data', + }; + + const source = new PotreeSource({ + file: fileName, + url: baseurl, + cloud, + }); + + const layer = new PotreeLayer('pointsCloud2', { source, crs: view.referenceCrs }); + View.prototype.addLayer.call(view, layer); + layer.whenReady.then((l) => { + assert.ok(l.material.defines.NORMAL); + assert.ok(!l.material.defines.NORMAL_SPHEREMAPPED); + assert.ok(!l.material.defines.NORMAL_OCT16); + done(); + }).catch(done); }); - layers.forEach(p => View.prototype.addLayer.call(view, p)); + it('cloud with spheremapped normals', function _it(done) { + // // spheremapped normals + const cloud = { + boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, + scale: 1.0, + pointAttributes: ['POSITION', 'COLOR_PACKED', 'NORMAL_SPHEREMAPPED'], + octreeDir: 'data', + }; + const source = new PotreeSource({ + file: fileName, + url: baseurl, + cloud, + }); + const layer = new PotreeLayer('pointsCloud3', { source, crs: view.referenceCrs }); + View.prototype.addLayer.call(view, layer); - Promise.all([p1, p2, p3, p4]) - .then(() => done()) - .catch(done); + layer.whenReady.then((l) => { + assert.ok(!l.material.defines.NORMAL); + assert.ok(l.material.defines.NORMAL_SPHEREMAPPED); + assert.ok(!l.material.defines.NORMAL_OCT16); + done(); + }).catch(done); + }); + + it('cloud with oct16 normals', function _it(done) { + // // // oct16 normals + const cloud = { + boundingBox: { lx: 0, ly: 1, ux: 2, uy: 3 }, + scale: 1.0, + pointAttributes: ['POSITION', 'COLOR_PACKED', 'CLASSIFICATION', 'NORMAL_OCT16'], + octreeDir: 'data', + }; + const source = new PotreeSource({ + file: fileName, + url: baseurl, + cloud, + }); + const layer = new PotreeLayer('pointsCloud4', { source, crs: view.referenceCrs }); + View.prototype.addLayer.call(view, layer); + + layer.whenReady + .then((l) => { + assert.ok(!l.material.defines.NORMAL); + assert.ok(!l.material.defines.NORMAL_SPHEREMAPPED); + assert.ok(l.material.defines.NORMAL_OCT16); + done(); + }).catch(done); + }); + }); }); }); - describe('getObjectToUpdateForAttachedLayers', function () { it('should correctly no-parent for the root', function () { const meta = { diff --git a/test/unit/source.js b/test/unit/source.js index ffd2fba1cb..50a1208315 100644 --- a/test/unit/source.js +++ b/test/unit/source.js @@ -1,6 +1,6 @@ import { Matrix4 } from 'three'; import assert from 'assert'; -import Source from 'Source/Source'; +import Source, { supportedFetchers } from 'Source/Source'; import Layer from 'Layer/Layer'; import WFSSource from 'Source/WFSSource'; import WMTSSource from 'Source/WMTSSource'; @@ -11,12 +11,14 @@ import OrientedImageSource from 'Source/OrientedImageSource'; import C3DTilesSource from 'Source/C3DTilesSource'; import C3DTilesIonSource from 'Source/C3DTilesIonSource'; import Extent from 'Core/Geographic/Extent'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import sinon from 'sinon'; +import Fetcher from 'Provider/Fetcher'; -describe('Sources', function () { - // geojson url to parse - const urlGeojson = 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements/09-ariege/departement-09-ariege.geojson'; +import fileSource from '../data/filesource/featCollec_Polygone.geojson'; + +const tileset = {}; +describe('Sources', function () { const vendorSpecific = { buffer: 4096, format_options: 'dpi:300;quantizer:octree', @@ -211,14 +213,25 @@ describe('Sources', function () { let fetchedData; describe('FileSource', function () { - it('should instance FileSource and fetch file', function (done) { + let stubSuppFetcher; + before(function () { + stubSuppFetcher = sinon.stub(supportedFetchers, 'get'); + stubSuppFetcher.withArgs('application/json') + .callsFake(() => () => Promise.resolve(JSON.parse(fileSource))); + }); + + after(function () { + stubSuppFetcher.restore(); + }); + + it('should instance FileSource with no source.fetchedData', function _it(done) { + const urlFilesource = 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements/09-ariege/departement-09-ariege.geojson'; const source = new FileSource({ - url: urlGeojson, + url: urlFilesource, crs: 'EPSG:4326', format: 'application/json', extent: new Extent('EPSG:4326', 0, 20, 0, 20), zoom: { min: 0, max: 21 }, - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, }); source.whenReady @@ -227,16 +240,20 @@ describe('Sources', function () { assert.ok(source.urlFromExtent()); assert.ok(source.extentInsideLimit(extent)); assert.ok(source.fetchedData); - assert.ok(source.fetchedData); assert.ok(!source.features); assert.ok(source.isFileSource); fetchedData = source.fetchedData; - assert.equal(fetchedData.properties.nom, 'Ariège'); + assert.equal(fetchedData.features[0].properties.nom, 'Ariège_simplified'); done(); }).catch(done); }); - it('should instance FileSource with fetchedData and parse data with a layer', function (done) { + it('should instance FileSource with source.fetchedData and parse data with a layer', function (done) { + // TO DO need cleareance: what is this test for ? + // - testing instanceation Filesource when fetchedData and source.feature is already available ? + // - testing instanciate Layer ? + // - testing source.onLayerAdded ? + // - testing souce.loadData ? const source = new FileSource({ fetchedData, format: 'application/json', @@ -255,7 +272,7 @@ describe('Sources', function () { .then(() => { source.loadData([], layer) .then((featureCollection) => { - assert.equal(featureCollection.features[0].vertices.length, 3536); + assert.equal(featureCollection.features[0].vertices.length, 16); done(); }) .catch((err) => { @@ -270,6 +287,7 @@ describe('Sources', function () { const source = new FileSource({ features: { foo: 'bar', crs: 'EPSG:4326', extent, matrixWorld: new Matrix4() }, crs: 'EPSG:4326', + format: 'application/json', }); source.onLayerAdded({ out: { crs: source.crs } }); assert.ok(source.urlFromExtent(extent).startsWith('fake-file-url')); @@ -283,38 +301,40 @@ describe('Sources', function () { assert.throws(() => new FileSource({ crs: 'EPSG:4326' }), Error); }); - describe('should set the crs projection from features', function () { - it('with the crs', function () { - const source = new FileSource({ - features: { crs: 'EPSG:4326' }, - }); - assert.strictEqual(source.crs, 'EPSG:4326'); - }); - - it('with the crs projection', function () { - const source = new FileSource({ - features: { crs: 'EPSG:4326' }, - }); - assert.strictEqual(source.crs, 'EPSG:4326'); + it('should set the crs projection from features', function () { + const source = new FileSource({ + features: { crs: 'EPSG:4326' }, + format: 'application/json', }); + assert.strictEqual(source.crs, 'EPSG:4326'); }); }); describe('C3DTilesSource', function () { - const params3DTiles = { - url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/3DTiles/lyon_1_4978/tileset.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, - }; + let stubFetcherJson; + before(function () { + stubFetcherJson = sinon.stub(Fetcher, 'json') + .callsFake(() => Promise.resolve(tileset)); + }); + after(function () { + stubFetcherJson.restore(); + }); it('should throw an error for having no required parameters', function () { assert.throws(() => new C3DTilesSource({}), Error); }); - it('should instance C3DTilesSource', function () { - const source = new C3DTilesSource(params3DTiles); - assert.ok(source.isC3DTilesSource); - assert.strictEqual(source.url, params3DTiles.url); - assert.strictEqual(source.baseUrl, 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/3DTiles/lyon_1_4978/'); + it('should instance C3DTilesSource', function (done) { + const url3dTileset = 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/' + + '3DTiles/lyon_1_4978/tileset.json'; + const source = new C3DTilesSource({ url: url3dTileset }); + source.whenReady + .then(() => { + assert.ok(source.isC3DTilesSource); + assert.strictEqual(source.url, url3dTileset); + assert.strictEqual(source.baseUrl, url3dTileset.slice(0, url3dTileset.lastIndexOf('/') + 1)); + done(); + }).catch(done); }); }); diff --git a/test/unit/style.js b/test/unit/style.js index 5f932f8fb3..53acb8d9a3 100644 --- a/test/unit/style.js +++ b/test/unit/style.js @@ -1,29 +1,38 @@ import { FEATURE_TYPES } from 'Core/Feature'; import Style from 'Core/Style'; import assert from 'assert'; -import Fetcher from 'Provider/Fetcher'; import { TextureLoader } from 'three'; +import Fetcher from 'Provider/Fetcher'; +import sinon from 'sinon'; + +describe('Style', function () { + const textureLoader = new TextureLoader(); + let stubFetcherTexture; + before(function () { + stubFetcherTexture = sinon.stub(Fetcher, 'texture') + .callsFake((url, options = {}) => { + let res; + let rej; -const textureLoader = new TextureLoader(); -Fetcher.texture = (url, options = {}) => { - let res; - let rej; + textureLoader.crossOrigin = options.crossOrigin; - textureLoader.crossOrigin = options.crossOrigin; + const promise = new Promise((resolve, reject) => { + res = resolve; + rej = reject; + }); - const promise = new Promise((resolve, reject) => { - res = resolve; - rej = reject; + textureLoader.load(url, (x) => { + x.image = document.createElement('img'); + return res(x); + }, () => {}, rej); + return promise; + }); }); - textureLoader.load(url, (x) => { - x.image = document.createElement('img'); - return res(x); - }, () => {}, rej); - return promise; -}; + after(function () { + stubFetcherTexture.restore(); + }); -describe('Style', function () { const style = new Style(); style.point.color = 'red'; style.fill.color = 'blue'; diff --git a/test/unit/vectortiles.js b/test/unit/vectortiles.js index 80d9366395..4464ab319d 100644 --- a/test/unit/vectortiles.js +++ b/test/unit/vectortiles.js @@ -1,28 +1,51 @@ import fs from 'fs'; import assert from 'assert'; -import { HttpsProxyAgent } from 'https-proxy-agent'; import VectorTileParser from 'Parser/VectorTileParser'; import VectorTilesSource from 'Source/VectorTilesSource'; import Extent from 'Core/Geographic/Extent'; import urlParser from 'Parser/MapBoxUrlParser'; +import Fetcher from 'Provider/Fetcher'; +import sinon from 'sinon'; + +import style from '../data/vectortiles/style.json'; +import tilejson from '../data/vectortiles/tilejson.json'; +import sprite from '../data/vectortiles/sprite.json'; + +const resources = { + 'test/data/vectortiles/style.json': style, + 'https://test/tilejson.json': tilejson, + 'https://test/sprite.json': sprite, +}; + +function parse(pbf, layers) { + return VectorTileParser.parse(pbf, { + in: { + layers, + styles: [[]], + }, + out: { + crs: 'EPSG:3857', + }, + }); +} describe('Vector tiles', function () { - // this PBF file comes from https://github.com/mapbox/vector-tile-js - // it contains two square polygons - const multipolygon = fs.readFileSync('test/data/pbf/multipolygon.pbf'); - multipolygon.extent = new Extent('TMS', 1, 1, 1); - - function parse(pbf, layers) { - return VectorTileParser.parse(pbf, { - in: { - layers, - styles: [[]], - }, - out: { - crs: 'EPSG:3857', - }, - }); - } + let stub; + let multipolygon; + + before(function () { + // this PBF file comes from https://github.com/mapbox/vector-tile-js + // it contains two square polygons + multipolygon = fs.readFileSync('test/data/pbf/multipolygon.pbf'); + multipolygon.extent = new Extent('TMS', 1, 1, 1); + + stub = sinon.stub(Fetcher, 'json') + .callsFake(url => Promise.resolve(JSON.parse(resources[url]))); + }); + + after(function () { + stub.restore(); + }); it('returns two squares', (done) => { parse(multipolygon, { @@ -78,7 +101,7 @@ describe('Vector tiles', function () { it('reads tiles URL from the style', (done) => { const source = new VectorTilesSource({ style: { - sources: { geojson: { tiles: ['http://server.geo/{z}/{x}/{y}.pbf'] } }, + sources: { tilesurl: { tiles: ['http://server.geo/{z}/{x}/{y}.pbf'] } }, layers: [], }, }); @@ -93,7 +116,7 @@ describe('Vector tiles', function () { const source = new VectorTilesSource({ url: 'fakeurl', style: { - sources: { geojson: {} }, + sources: { tilejson: {} }, layers: [{ type: 'background' }], }, }); @@ -107,7 +130,7 @@ describe('Vector tiles', function () { const source = new VectorTilesSource({ url: 'fakeurl', style: { - sources: { geojson: {} }, + sources: { tilejson: {} }, layers: [{ id: 'land', type: 'fill', @@ -124,25 +147,25 @@ describe('Vector tiles', function () { }).catch(done); }); - it('loads the style from a file', (done) => { + it('loads the style from a file', function _it(done) { const source = new VectorTilesSource({ - style: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/vectortiles/style.json', - networkOptions: process.env.HTTPS_PROXY ? { agent: new HttpsProxyAgent(process.env.HTTPS_PROXY) } : {}, + style: 'test/data/vectortiles/style.json', }); - source.whenReady.then(() => { - assert.equal(source.styles.land.fill.color, 'rgb(255,0,0)'); - assert.equal(source.styles.land.fill.opacity, 1); - assert.equal(source.styles.land.zoom.min, 5); - assert.equal(source.styles.land.zoom.max, 13); - done(); - }).catch(done); + source.whenReady + .then(() => { + assert.equal(source.styles.land.fill.color, 'rgb(255,0,0)'); + assert.equal(source.styles.land.fill.opacity, 1); + assert.equal(source.styles.land.zoom.min, 5); + assert.equal(source.styles.land.zoom.max, 13); + done(); + }).catch(done); }); it('sets the correct Style#zoom.min', (done) => { const source = new VectorTilesSource({ url: 'fakeurl', style: { - sources: { geojson: {} }, + sources: { tilejson: {} }, layers: [{ // minzoom is 0 (default value) id: 'first',