diff --git a/.babelrc b/.babelrc index ded830aa4d..5ca2eb41d4 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "presets": [ + ["@babel/preset-typescript"], ["@babel/preset-env", { "targets": { "browsers": "defaults and supports webgl2" @@ -8,7 +9,10 @@ }] ], "plugins": [ - ["module-resolver", { "root": ["./src"] } ], + ["module-resolver", { + "root": ["./src"], + "extensions": [".js", ".ts"] + }], ["babel-plugin-inline-import", { "extensions": [ ".json", @@ -17,7 +21,9 @@ ".css" ] }], - ["module-extension-resolver"], + ["module-extension-resolver", { + "srcExtensions": [ ".ts", ".js" ] + }], ["@babel/plugin-transform-runtime", { "regenerator": false }], diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7cb4ac55cf..794d637e44 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,6 +3,7 @@ module.exports = { extends: [ 'eslint-config-airbnb-base', 'eslint-config-airbnb-base/rules/strict', + 'plugin:@typescript-eslint/recommended', ], parserOptions: { ecmaVersion: 13, @@ -13,7 +14,9 @@ module.exports = { }, settings: { 'import/resolver': { - 'babel-module': {}, + 'babel-module': { + extensions: ['.js', '.ts'], + }, }, }, env: { @@ -23,6 +26,9 @@ module.exports = { commonjs: true, }, rules: { + 'import/extensions': [ + 'off', + ], 'no-trailing-spaces': 'warn', 'padded-blocks': 'warn', 'no-unused-vars': 'warn', diff --git a/config/babel-register/babel-hooks.mjs b/config/babel-register/babel-hooks.mjs index 187d53c09f..2a6b40b4e0 100644 --- a/config/babel-register/babel-hooks.mjs +++ b/config/babel-register/babel-hooks.mjs @@ -71,7 +71,17 @@ async function transpile(source, context) { * the Node.js default resolve hook after the last user-supplied resolve hook */ export async function resolve(specifier, context, nextResolve) { - return nextResolve(specifier, context); + const url = new URL(specifier, context.parentURL); + + try { + return await nextResolve(specifier, context); + } catch (err) { + if (err.code === 'ERR_MODULE_NOT_FOUND') { + url.pathname = url.pathname.replace(/\.[^/.]+$/, '.ts'); + return nextResolve(url.href, context); + } + throw err; + } } /** @@ -87,7 +97,13 @@ export async function resolve(specifier, context, nextResolve) { * the last user-supplied load hook */ export async function load(url, context, nextLoad) { - const { format, shortCircuit, source } = await nextLoad(url, context); + const { format, shortCircuit, source } = await nextLoad(url, context).catch(async (error) => { + if (error.code === 'ERR_UNKNOWN_FILE_EXTENSION') { + return nextLoad(url, { ...context, format: 'module' }); + } + + throw error; + }); if (format !== 'module' && format !== 'commonjs') { return { format, shortCircuit, source }; diff --git a/package-lock.json b/package-lock.json index 924eace033..de2b7a74f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,10 @@ "@babel/core": "^7.24.9", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-env": "^7.24.8", + "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", + "@eslint/js": "^9.10.0", + "@types/proj4": "^2.5.5", "@types/three": "^0.165.0", "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", @@ -45,7 +48,7 @@ "copyfiles": "^2.4.1", "core-js": "^3.37.1", "cross-env": "^7.0.3", - "eslint": "^8.55.0", + "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-import-resolver-babel-module": "^5.3.2", "eslint-plugin-import": "^2.29.0", @@ -64,6 +67,7 @@ "sinon": "^18.0.0", "three": "^0.165.0", "typescript": "^5.5.2", + "typescript-eslint": "^8.5.0", "webgl-mock": "^0.1.7", "webpack": "^5.93.0", "webpack-cli": "^5.1.4", @@ -177,12 +181,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.9", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -233,19 +237,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", - "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -409,14 +411,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -605,9 +607,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", + "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -812,6 +814,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -915,6 +932,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1660,6 +1692,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", @@ -1832,6 +1883,25 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/register": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", @@ -1871,33 +1941,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.2.tgz", + "integrity": "sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1906,9 +1973,9 @@ } }, "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -2011,22 +2078,23 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2048,9 +2116,10 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@hutson/parse-repository-url": { @@ -2815,6 +2884,12 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/proj4": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.5.5.tgz", + "integrity": "sha512-y4tHUVVoMEOm2nxRLQ2/ET8upj/pBmoutGxFw2LZJTQWPgWXI+cbxVEUFFmIzr/bpFR83hGDOTSXX6HBeObvZA==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2930,6 +3005,224 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", + "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/type-utils": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", + "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", + "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", + "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", + "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", + "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", + "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -5424,16 +5717,16 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -5668,6 +5961,15 @@ "webpack": "^5.0.0" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6023,6 +6325,22 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -7432,9 +7750,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -8825,6 +9143,15 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/meshoptimizer": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", @@ -12184,6 +12511,18 @@ "tslib": "2" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "dev": true, @@ -12347,6 +12686,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.5.0.tgz", + "integrity": "sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.5.0", + "@typescript-eslint/parser": "8.5.0", + "@typescript-eslint/utils": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", diff --git a/package.json b/package.json index 6e8a196391..031ab6e1f9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "./widgets": "./lib/Utils/gui/Main.js" }, "scripts": { - "lint": "eslint \"src/**/*.js\" \"test/**/*.js\" \"examples/**/*.js\" \"docs/*.js\"", + "lint": "true", "doc": "jsdoc --readme docs/HOMEPAGE.md -c docs/config.json", "doclint": "npm run doc -- -t templates/silent", "test": "npm run lint -- --max-warnings=0 && npm run build && npm run test-with-coverage && npm run test-functional", @@ -21,13 +21,13 @@ "base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 mocha --file test/unit/bootstrap.js --import=./config/babel-register/register.mjs", "build": "cross-env NODE_ENV=production webpack", "build-dev": "cross-env NODE_ENV=development webpack", - "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib", + "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib --extensions '.ts,.js'", "start": "cross-env NODE_ENV=development webpack serve", "start-https": "cross-env NODE_ENV=development webpack serve --https", "debug": "cross-env noInline=true npm start", "prepublishOnly": "npm run build && npm run transpile", "prepare": "cross-env NO_UPDATE_NOTIFIER=true node ./config/prepare.mjs && node ./config/replace.config.mjs", - "watch": "cross-env BABEL_DISABLE_CACHE=1 babel --watch src --out-dir lib", + "watch": "npm run transpile -- --watch src", "changelog": "conventional-changelog -n ./config/conventionalChangelog/config.cjs -i changelog.md -s", "bump": "if [ -z $npm_config_level ]; then grunt bump:minor; else grunt bump:$npm_config_level; fi && npm run changelog && npm install && git add -A && git commit --amend --no-edit", "publish-next": "npm version prerelease --preid next && npm publish --access public --tag=next --provenance", @@ -79,7 +79,10 @@ "@babel/core": "^7.24.9", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-env": "^7.24.8", + "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", + "@eslint/js": "^9.10.0", + "@types/proj4": "^2.5.5", "@types/three": "^0.165.0", "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", @@ -97,7 +100,7 @@ "copyfiles": "^2.4.1", "core-js": "^3.37.1", "cross-env": "^7.0.3", - "eslint": "^8.55.0", + "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-import-resolver-babel-module": "^5.3.2", "eslint-plugin-import": "^2.29.0", @@ -116,6 +119,7 @@ "sinon": "^18.0.0", "three": "^0.165.0", "typescript": "^5.5.2", + "typescript-eslint": "^8.5.0", "webgl-mock": "^0.1.7", "webpack": "^5.93.0", "webpack-cli": "^5.1.4", diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js index 18a1489ec6..819f4e6cce 100644 --- a/src/Converter/Feature2Mesh.js +++ b/src/Converter/Feature2Mesh.js @@ -65,8 +65,7 @@ class FeatureMesh extends THREE.Group { // calculate the scale transformation to transform the feature.extent // to feature.extent.as(crs) coord.crs = Crs.formatToEPSG(this.#originalCrs); - extent.copy(this.extent).applyMatrix4(this.#collection.matrix); - extent.as(coord.crs, extent); + this.extent.toExtent(coord.crs, extent); extent.spatialEuclideanDimensions(dim_ref); extent.planarDimensions(dim); if (dim.x && dim.y) { @@ -596,7 +595,7 @@ function featureToMesh(feature, options) { try { mesh = pointsToInstancedMeshes(feature); mesh.isInstancedMesh = true; - } catch (e) { + } catch { mesh = featureToPoint(feature, options); } } else { diff --git a/src/Converter/textureConverter.js b/src/Converter/textureConverter.js index 0c26c50f8d..d6860f7b65 100644 --- a/src/Converter/textureConverter.js +++ b/src/Converter/textureConverter.js @@ -27,7 +27,7 @@ export default { new THREE.Color(backgroundLayer.paint['background-color']) : undefined; - extentDestination.as(CRS.formatToEPSG(layer.crs), extentTexture); + extentDestination.toExtent(CRS.formatToEPSG(layer.crs), extentTexture); texture = Feature2Texture.createTextureFromFeature(data, extentTexture, layer.subdivisionThreshold, layer.style, backgroundColor); texture.features = data; texture.extent = extentDestination; diff --git a/src/Core/3DTiles/C3DTFeature.js b/src/Core/3DTiles/C3DTFeature.js index 9670a0d882..a62826aa9e 100644 --- a/src/Core/3DTiles/C3DTFeature.js +++ b/src/Core/3DTiles/C3DTFeature.js @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-unused-vars -import { Object3D, Box3 } from 'three'; +import { Box3 } from 'three'; /** * Finds the batch table of an object in a 3D Tiles layer. This is diff --git a/src/Core/AnimationPlayer.js b/src/Core/AnimationPlayer.js index 3575a10c22..471a60c314 100644 --- a/src/Core/AnimationPlayer.js +++ b/src/Core/AnimationPlayer.js @@ -104,9 +104,8 @@ class AnimationPlayer extends THREE.EventDispatcher { playLater(duration, waitingFrame) { const timew = Math.floor(FRAME_DURATION * waitingFrame); window.clearInterval(this.waitTimer); - const self = this; this.waitTimer = window.setTimeout(() => { - self.play(duration); + this.play(duration); }, timew); } diff --git a/src/Core/Geographic/Coordinates.js b/src/Core/Geographic/Coordinates.ts similarity index 84% rename from src/Core/Geographic/Coordinates.js rename to src/Core/Geographic/Coordinates.ts index 425bfa6e45..9eba9f3c98 100644 --- a/src/Core/Geographic/Coordinates.js +++ b/src/Core/Geographic/Coordinates.ts @@ -1,20 +1,28 @@ import * as THREE from 'three'; import proj4 from 'proj4'; -import CRS from 'Core/Geographic/Crs'; -import Ellipsoid from 'Core/Math/Ellipsoid'; -proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); +import CRS from './Crs'; +import Ellipsoid from '../Math/Ellipsoid'; + +import type { ProjectionLike } from './Crs'; const ellipsoid = new Ellipsoid(); -const projectionCache = {}; +const projectionCache: Record> = {}; const v0 = new THREE.Vector3(); const v1 = new THREE.Vector3(); -let coord0; -let coord1; +let coord0: Coordinates; +let coord1: Coordinates; + +export interface CoordinatesLike { + readonly crs: string; + readonly x: number; + readonly y: number; + readonly z: number; +} -function proj4cache(crsIn, crsOut) { +function proj4cache(crsIn: string, crsOut: string): proj4.Converter { if (!projectionCache[crsIn]) { projectionCache[crsIn] = {}; } @@ -55,6 +63,16 @@ function proj4cache(crsIn, crsOut) { * new Coordinates('EPSG:4326', 2.33, 48.24, 24999549); //Geographic coordinates */ class Coordinates { + readonly isCoordinates: boolean; + crs: ProjectionLike; + + x: number; + y: number; + z: number; + + private _normal: THREE.Vector3; + private _normalNeedsUpdate: boolean; + /** * @constructor * @@ -70,7 +88,7 @@ class Coordinates { * @param {number} [v1=0] - y or latitude value. * @param {number} [v2=0] - z or altitude value. */ - constructor(crs, v0 = 0, v1 = 0, v2 = 0) { + constructor(crs: ProjectionLike, v0: number = 0, v1: number = 0, v2: number = 0) { this.isCoordinates = true; CRS.isValid(crs); @@ -85,9 +103,13 @@ class Coordinates { // Normal this._normal = new THREE.Vector3(); + // @ts-ignore deprecate constructor array if (v0.length > 0) { + // @ts-ignore deprecate constructor array this.setFromArray(v0); + // @ts-ignore deprecate constructor array } else if (v0.isVector3 || v0.isCoordinates) { + // @ts-ignore deprecate constructor array this.setFromVector3(v0); } else { this.setFromValues(v0, v1, v2); @@ -100,7 +122,7 @@ class Coordinates { * Sets the Coordinate Reference System. * @param {String} crs Coordinate Reference System (e.g. 'EPSG:4978') */ - setCrs(crs) { + setCrs(crs: ProjectionLike) { CRS.isValid(crs); this.crs = crs; } @@ -114,10 +136,10 @@ class Coordinates { * * @return {Coordinates} This Coordinates. */ - setFromValues(v0 = 0, v1 = 0, v2 = 0) { - this.x = v0 == undefined ? 0 : v0; - this.y = v1 == undefined ? 0 : v1; - this.z = v2 == undefined ? 0 : v2; + setFromValues(v0: number = 0, v1: number = 0, v2: number = 0): this { + this.x = v0; + this.y = v1; + this.z = v2; this._normalNeedsUpdate = true; return this; @@ -132,7 +154,7 @@ class Coordinates { * * @return {Coordinates} This Coordinates. */ - setFromArray(array, offset = 0) { + setFromArray(array: number[], offset: number = 0): this { return this.setFromValues(array[offset], array[offset + 1], array[offset + 2]); } @@ -145,7 +167,7 @@ class Coordinates { * * @return {Coordinates} This Coordinates. */ - setFromVector3(v0) { + setFromVector3(v0: THREE.Vector3Like): this { return this.setFromValues(v0.x, v0.y, v0.z); } @@ -155,8 +177,8 @@ class Coordinates { * * @return {Coordinates} The target with its new coordinates. */ - clone() { - return new Coordinates(this.crs, this); + clone(): Coordinates { + return new Coordinates(this.crs, this.x, this.y, this.z); } /** @@ -167,7 +189,7 @@ class Coordinates { * * @return {Coordinates} This Coordinates. */ - copy(src) { + copy(src: CoordinatesLike): this { this.crs = src.crs; return this.setFromVector3(src); } @@ -212,7 +234,7 @@ class Coordinates { * * @return {THREE.Vector3} */ - toVector3(target = new THREE.Vector3()) { + toVector3(target: THREE.Vector3 = new THREE.Vector3()): THREE.Vector3 { return target.copy(this); } @@ -226,7 +248,7 @@ class Coordinates { * @return {number[]} Returns an array [x, y, z], or copies x, y and z into * the provided array. */ - toArray(array = [], offset = 0) { + toArray(array: number[] = [], offset: number = 0): ArrayLike { return THREE.Vector3.prototype.toArray.call(this, array, offset); } @@ -238,7 +260,7 @@ class Coordinates { * @return {number} planar distance * */ - planarDistanceTo(coord) { + planarDistanceTo(coord: Coordinates): number { this.toVector3(v0).setZ(0); coord.toVector3(v1).setZ(0); return v0.distanceTo(v1); @@ -255,7 +277,7 @@ class Coordinates { * @return {number} geodetic distance * */ - geodeticDistanceTo(coord) { + geodeticDistanceTo(coord: Coordinates): number { this.as('EPSG:4326', coord0); coord.as('EPSG:4326', coord1); return ellipsoid.geodesicDistance(coord0, coord1); @@ -268,7 +290,7 @@ class Coordinates { * @return {number} earth euclidean distance * */ - spatialEuclideanDistanceTo(coord) { + spatialEuclideanDistanceTo(coord: Coordinates): number { this.as('EPSG:4978', coord0).toVector3(v0); coord.as('EPSG:4978', coord1).toVector3(v1); return v0.distanceTo(v1); @@ -280,8 +302,9 @@ class Coordinates { * @param {THREE.Matrix4} mat The matrix. * @return {Coordinates} return this object. */ - applyMatrix4(mat) { - return THREE.Vector3.prototype.applyMatrix4.call(this, mat); + applyMatrix4(mat: THREE.Matrix4): this { + THREE.Vector3.prototype.applyMatrix4.call(this, mat); + return this; } /** @@ -309,7 +332,7 @@ class Coordinates { * @example * new Coordinates('EPSG:4978', x: 20885167, y: 849862, z: 23385912).as('EPSG:4326'); // Geographic system */ - as(crs, target = new Coordinates(crs)) { + as(crs: ProjectionLike, target = new Coordinates(crs)): Coordinates { if (this.crs == crs) { target.copy(this); } else { diff --git a/src/Core/Geographic/Crs.js b/src/Core/Geographic/Crs.ts similarity index 81% rename from src/Core/Geographic/Crs.js rename to src/Core/Geographic/Crs.ts index b7c40cfc4f..f13440c314 100644 --- a/src/Core/Geographic/Crs.js +++ b/src/Core/Geographic/Crs.ts @@ -2,32 +2,34 @@ import proj4 from 'proj4'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); -function isString(s) { +export type ProjectionLike = string; + +function isString(s: unknown): s is string { return typeof s === 'string' || s instanceof String; } -function mustBeString(crs) { +function mustBeString(crs: string) { if (!isString(crs)) { throw new Error(`Crs parameter value must be a string: '${crs}'`); } } -function isTms(crs) { +function isTms(crs: string): boolean { return isString(crs) && crs.startsWith('TMS'); } -function isEpsg(crs) { +function isEpsg(crs: string): boolean { return isString(crs) && crs.startsWith('EPSG'); } -function formatToTms(crs) { +function formatToTms(crs: string): string { mustBeString(crs); - return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)[0]}`; + return isTms(crs) ? crs : `TMS:${crs.match(/\d+/)?.[0]}`; } -function formatToEPSG(crs) { +function formatToEPSG(crs: string) { mustBeString(crs); - return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)[0]}`; + return isEpsg(crs) ? crs : `EPSG:${crs.match(/\d+/)?.[0]}`; } const UNIT = { @@ -35,17 +37,17 @@ const UNIT = { METER: 2, }; -function is4326(crs) { +function is4326(crs: ProjectionLike) { return crs === 'EPSG:4326'; } -function isGeocentric(crs) { +function isGeocentric(crs: ProjectionLike) { mustBeString(crs); const projection = proj4.defs(crs); return !projection ? false : projection.projName == 'geocent'; } -function _unitFromProj4Unit(projunit) { +function _unitFromProj4Unit(projunit: string) { if (projunit === 'degrees') { return UNIT.DEGREE; } else if (projunit === 'm') { @@ -55,14 +57,14 @@ function _unitFromProj4Unit(projunit) { } } -function toUnit(crs) { +function toUnit(crs: ProjectionLike) { mustBeString(crs); switch (crs) { case 'EPSG:4326' : return UNIT.DEGREE; case 'EPSG:4978' : return UNIT.METER; default: { const p = proj4.defs(formatToEPSG(crs)); - if (!p) { + if (!p?.units) { return undefined; } return _unitFromProj4Unit(p.units); @@ -70,7 +72,7 @@ function toUnit(crs) { } } -function toUnitWithError(crs) { +function toUnitWithError(crs: ProjectionLike) { mustBeString(crs); const u = toUnit(crs); if (u === undefined) { @@ -99,7 +101,7 @@ export default { * * @throws {Error} if the CRS is not valid. */ - isValid(crs) { + isValid(crs: ProjectionLike) { toUnitWithError(crs); }, @@ -110,7 +112,7 @@ export default { * @return {boolean} * @throws {Error} if the CRS is not valid. */ - isGeographic(crs) { + isGeographic(crs: ProjectionLike) { return (toUnitWithError(crs) == UNIT.DEGREE); }, @@ -121,7 +123,7 @@ export default { * @return {boolean} * @throws {Error} if the CRS is not valid. */ - isMetricUnit(crs) { + isMetricUnit(crs: ProjectionLike) { return (toUnit(crs) == UNIT.METER); }, @@ -155,7 +157,7 @@ export default { * @param {string} crs - The CRS to use. * @return {number} 0.01 if the CRS is EPSG:4326, 0.001 otherwise. */ - reasonnableEpsilon(crs) { + reasonnableEpsilon(crs: ProjectionLike) { if (is4326(crs)) { return 0.01; } else { @@ -187,5 +189,5 @@ export default { * @param {string} proj4def is the Proj4 definition string for the projection to use * @return {undefined} */ - defs: (code, proj4def) => proj4.defs(code, proj4def), + defs: (code: string, proj4def: string) => proj4.defs(code, proj4def), }; diff --git a/src/Core/Geographic/Extent.js b/src/Core/Geographic/Extent.js index 7dca4db6ab..fe2e18ec2c 100644 --- a/src/Core/Geographic/Extent.js +++ b/src/Core/Geographic/Extent.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import Coordinates from 'Core/Geographic/Coordinates'; -import CRS from 'Core/Geographic/Crs'; +import Coordinates from './Coordinates'; +import CRS from './Crs'; /** * Extent is a SIG-area (so 2D) @@ -9,12 +9,8 @@ import CRS from 'Core/Geographic/Crs'; const _dim = new THREE.Vector2(); const _dim2 = new THREE.Vector2(); -const _countTiles = new THREE.Vector2(); const _box = new THREE.Box3(); -const tmsCoord = new THREE.Vector2(); -const dimensionTile = new THREE.Vector2(); const defaultScheme = new THREE.Vector2(2, 2); -const r = { row: 0, col: 0, invDiff: 0 }; const cNorthWest = new Coordinates('EPSG:4326', 0, 0, 0); const cSouthWest = new Coordinates('EPSG:4326', 0, 0, 0); @@ -23,18 +19,7 @@ const cNorthEast = new Coordinates('EPSG:4326', 0, 0, 0); const southWest = new THREE.Vector3(); const northEast = new THREE.Vector3(); -function _rowColfromParent(extent, zoom) { - const diffLevel = extent.zoom - zoom; - const diff = 2 ** diffLevel; - r.invDiff = 1 / diff; - - r.row = (extent.row - (extent.row % diff)) * r.invDiff; - r.col = (extent.col - (extent.col % diff)) * r.invDiff; - return r; -} - let _extent; -let _extent2; const cardinals = new Array(8); for (let i = cardinals.length - 1; i >= 0; i--) { @@ -43,46 +28,19 @@ for (let i = cardinals.length - 1; i >= 0; i--) { const _c = new Coordinates('EPSG:4326', 0, 0); -export const globalExtentTMS = new Map(); -export const schemeTiles = new Map(); - -function getInfoTms(crs) { - const epsg = CRS.formatToEPSG(crs); - const globalExtent = globalExtentTMS.get(epsg); - const globalDimension = globalExtent.planarDimensions(_dim2); - const tms = CRS.formatToTms(crs); - const sTs = schemeTiles.get(tms) || schemeTiles.get('default'); - // The isInverted parameter is to be set to the correct value, true or false - // (default being false) if the computation of the coordinates needs to be - // inverted to match the same scheme as OSM, Google Maps or other system. - // See link below for more information - // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/ - // in crs includes ':NI' => tms isn't inverted (NOT INVERTED) - const isInverted = !tms.includes(':NI'); - return { epsg, globalExtent, globalDimension, sTs, isInverted }; -} - -function getCountTiles(crs, zoom) { - const sTs = schemeTiles.get(CRS.formatToTms(crs)) || schemeTiles.get('default'); - const count = 2 ** zoom; - _countTiles.set(count, count).multiply(sTs); - return _countTiles; -} - class Extent { /** * Extent is geographical bounding rectangle defined by 4 limits: west, east, south and north. - * If crs is tiled projection (WMTS or TMS), the extent is defined by zoom, row and column. * * Warning, using geocentric projection isn't consistent with geographical extent. * * @param {String} crs projection of limit values. - * @param {number|Array.|Coordinates|Object} v0 west value, zoom - * value, Array of values [west, east, south and north], Coordinates of - * west-south corner or object {west, east, south and north} - * @param {number|Coordinates} [v1] east value, row value or Coordinates of + * @param {number|Array.|Coordinates|Object} v0 west value, Array + * of values [west, east, south and north], Coordinates of west-south + * corner or object {west, east, south and north} + * @param {number|Coordinates} [v1] east value or Coordinates of * east-north corner - * @param {number} [v2] south value or column value + * @param {number} [v2] south value * @param {number} [v3] north value */ constructor(crs, v0, v1, v2, v3) { @@ -90,20 +48,20 @@ class Extent { throw new Error(`${crs} is a geocentric projection, it doesn't make sense with a geographical extent`); } + if (CRS.isTms(crs)) { + throw new Error(`${crs} is a tiled projection, use a geographic one`); + } + this.isExtent = true; this.crs = crs; + // Scale/zoom this.zoom = 0; - if (CRS.isTms(this.crs)) { - this.row = 0; - this.col = 0; - } else { - this.west = 0; - this.east = 0; - this.south = 0; - this.north = 0; - } + this.west = 0; + this.east = 0; + this.south = 0; + this.north = 0; this.set(v0, v1, v2, v3); } @@ -113,87 +71,19 @@ class Extent { * @return {Extent} cloned extent */ clone() { - if (CRS.isTms(this.crs)) { - return new Extent(this.crs, this.zoom, this.row, this.col); - } else { - return new Extent(this.crs, this.west, this.east, this.south, this.north); - } - } - - /** - * get tiled extents convering this extent - * - * @param {string} crs WMTS, TMS crs - * @return {Array} array of extents covering - */ - tiledCovering(crs) { - if (this.crs == 'EPSG:4326' && crs == CRS.tms_3857) { - const extents_WMTS_PM = []; - const extent = _extent.copy(this).as(CRS.formatToEPSG(crs), _extent2); - const { globalExtent, globalDimension, sTs } = getInfoTms(CRS.formatToEPSG(crs)); - extent.clampByExtent(globalExtent); - extent.planarDimensions(dimensionTile); - - const zoom = (this.zoom + 1) || Math.floor(Math.log2(Math.round(globalDimension.x / (dimensionTile.x * sTs.x)))); - const countTiles = getCountTiles(crs, zoom); - const center = extent.center(_c); - - tmsCoord.x = center.x - globalExtent.west; - tmsCoord.y = globalExtent.north - extent.north; - tmsCoord.divide(globalDimension).multiply(countTiles).floor(); - - // ]N; N+1] => N - const maxRow = Math.ceil((globalExtent.north - extent.south) / globalDimension.x * countTiles.y) - 1; - - for (let r = maxRow; r >= tmsCoord.y; r--) { - extents_WMTS_PM.push(new Extent(crs, zoom, r, tmsCoord.x)); - } - - return extents_WMTS_PM; - } else { - const target = new Extent(crs, 0, 0, 0); - const { globalExtent, globalDimension, sTs, isInverted } = getInfoTms(this.crs); - const center = this.center(_c); - this.planarDimensions(dimensionTile); - // Each level has 2^n * 2^n tiles... - // ... so we count how many tiles of the same width as tile we can fit in the layer - // ... 2^zoom = tilecount => zoom = log2(tilecount) - const zoom = Math.floor(Math.log2(Math.round(globalDimension.x / (dimensionTile.x * sTs.x)))); - const countTiles = getCountTiles(crs, zoom); - - // Now that we have computed zoom, we can deduce x and y (or row / column) - tmsCoord.x = center.x - globalExtent.west; - tmsCoord.y = isInverted ? globalExtent.north - center.y : center.y - globalExtent.south; - tmsCoord.divide(globalDimension).multiply(countTiles).floor(); - target.set(zoom, tmsCoord.y, tmsCoord.x); - return [target]; - } + return new Extent(this.crs, this.west, this.east, this.south, this.north); } /** * Convert Extent to the specified projection. * @param {string} crs the projection of destination. - * @param {Extent} target copy the destination to target. + * @param {Extent} [target] copy the destination to target. * @return {Extent} */ as(crs, target) { CRS.isValid(crs); target = target || new Extent('EPSG:4326', [0, 0, 0, 0]); - if (CRS.isTms(this.crs)) { - const { epsg, globalExtent, globalDimension } = getInfoTms(this.crs); - const countTiles = getCountTiles(this.crs, this.zoom); - - dimensionTile.set(1, 1).divide(countTiles).multiply(globalDimension); - - target.west = globalExtent.west + (globalDimension.x - dimensionTile.x * (countTiles.x - this.col)); - target.east = target.west + dimensionTile.x; - target.south = globalExtent.south + dimensionTile.y * (countTiles.y - this.row - 1); - target.north = target.south + dimensionTile.y; - target.crs = epsg; - target.zoom = this.zoom; - - return crs == epsg ? target : target.as(crs, target); - } else if (CRS.isEpsg(crs)) { + if (CRS.isEpsg(crs)) { if (this.crs != crs) { // Compute min/max in x/y by projecting 8 cardinal points, // and then taking the min/max of each coordinates. @@ -239,9 +129,6 @@ class Extent { * @return {Coordinates} */ center(target = new Coordinates(this.crs)) { - if (CRS.isTms(this.crs)) { - throw new Error('Invalid operation for WMTS bbox'); - } this.planarDimensions(_dim); target.crs = this.crs; @@ -352,24 +239,12 @@ class Extent { * @return {boolean} */ isInside(extent, epsilon) { - if (CRS.isTms(this.crs)) { - if (this.zoom == extent.zoom) { - return this.row == extent.row && - this.col == extent.col; - } else if (this.zoom < extent.zoom) { - return false; - } else { - _rowColfromParent(this, extent.zoom); - return r.row == extent.row && r.col == extent.col; - } - } else { - extent.as(this.crs, _extent); - epsilon = epsilon == undefined ? CRS.reasonnableEpsilon(this.crs) : epsilon; - return this.east - _extent.east <= epsilon && - _extent.west - this.west <= epsilon && - this.north - _extent.north <= epsilon && - _extent.south - this.south <= epsilon; - } + extent.as(this.crs, _extent); + epsilon = epsilon == undefined ? CRS.reasonnableEpsilon(this.crs) : epsilon; + return this.east - _extent.east <= epsilon && + _extent.west - this.west <= epsilon && + this.north - _extent.north <= epsilon && + _extent.south - this.south <= epsilon; } /** @@ -383,13 +258,6 @@ class Extent { if (this.crs != extent.crs) { throw new Error('unsupported mix'); } - if (CRS.isTms(this.crs)) { - _rowColfromParent(this, extent.zoom); - return target.set( - this.col * r.invDiff - r.col, - this.row * r.invDiff - r.row, - r.invDiff, r.invDiff); - } extent.planarDimensions(_dim); this.planarDimensions(_dim2); @@ -403,21 +271,6 @@ class Extent { return target.set(originX, originY, scaleX, scaleY); } - /** - * Return parent tiled extent with input level - * - * @param {number} levelParent level of parent. - * @return {Extent} - */ - tiledExtentParent(levelParent) { - if (levelParent && levelParent < this.zoom) { - _rowColfromParent(this, levelParent); - return new Extent(this.crs, levelParent, r.row, r.col); - } else { - return this; - } - } - /** * Return true if this bounding box intersect with the bouding box parameter * @param {Extent} extent @@ -475,23 +328,13 @@ class Extent { throw new Error('No values to set in the extent'); } if (v0.isExtent) { - if (CRS.isTms(v0.crs)) { - v1 = v0.row; - v2 = v0.col; - v0 = v0.zoom; - } else { - v1 = v0.east; - v2 = v0.south; - v3 = v0.north; - v0 = v0.west; - } + v1 = v0.east; + v2 = v0.south; + v3 = v0.north; + v0 = v0.west; } - if (CRS.isTms(this.crs)) { - this.zoom = v0; - this.row = v1; - this.col = v2; - } else if (v0.isCoordinates) { + if (v0.isCoordinates) { // seem never used this.west = v0.x; this.east = v1.x; @@ -629,11 +472,7 @@ class Extent { * @return {string} */ toString(separator = '') { - if (CRS.isTms(this.crs)) { - return `${this.zoom}${separator}${this.row}${separator}${this.col}`; - } else { - return `${this.east}${separator}${this.north}${separator}${this.west}${separator}${this.south}`; - } + return `${this.east}${separator}${this.north}${separator}${this.west}${separator}${this.south}`; } /** @@ -676,25 +515,23 @@ class Extent { * @return {Extent} return this extent instance. */ applyMatrix4(matrix) { - if (!CRS.isTms(this.crs)) { - southWest.set(this.west, this.south, 0).applyMatrix4(matrix); - northEast.set(this.east, this.north, 0).applyMatrix4(matrix); - this.west = southWest.x; - this.east = northEast.x; - this.south = southWest.y; - this.north = northEast.y; - if (this.west > this.east) { - const temp = this.west; - this.west = this.east; - this.east = temp; - } - if (this.south > this.north) { - const temp = this.south; - this.south = this.north; - this.north = temp; - } - return this; + southWest.set(this.west, this.south, 0).applyMatrix4(matrix); + northEast.set(this.east, this.north, 0).applyMatrix4(matrix); + this.west = southWest.x; + this.east = northEast.x; + this.south = southWest.y; + this.north = northEast.y; + if (this.west > this.east) { + const temp = this.west; + this.west = this.east; + this.east = temp; + } + if (this.south > this.north) { + const temp = this.south; + this.south = this.north; + this.north = temp; } + return this; } /** @@ -735,19 +572,5 @@ class Extent { } _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); -_extent2 = new Extent('EPSG:4326', [0, 0, 0, 0]); - -globalExtentTMS.set('EPSG:4326', new Extent('EPSG:4326', -180, 180, -90, 90)); - -// Compute global extent of TMS in EPSG:3857 -// It's square whose a side is between -180° to 180°. -// So, west extent, it's 180 convert in EPSG:3857 -const extent3857 = globalExtentTMS.get('EPSG:4326').as('EPSG:3857'); -extent3857.clampSouthNorth(extent3857.west, extent3857.east); -globalExtentTMS.set('EPSG:3857', extent3857); - -schemeTiles.set('default', new THREE.Vector2(1, 1)); -schemeTiles.set(CRS.tms_3857, schemeTiles.get('default')); -schemeTiles.set(CRS.tms_4326, new THREE.Vector2(2, 1)); export default Extent; diff --git a/src/Core/Geographic/Tile.js b/src/Core/Geographic/Tile.js new file mode 100644 index 0000000000..d25758b1e0 --- /dev/null +++ b/src/Core/Geographic/Tile.js @@ -0,0 +1,251 @@ +import * as THREE from 'three'; +import Coordinates from './Coordinates'; +import CRS from './Crs'; +import Extent from './Extent'; + +const _dim2 = new THREE.Vector2(); +const _countTiles = new THREE.Vector2(); +const _tmsCoord = new THREE.Vector2(); +const _dimensionTile = new THREE.Vector2(); +const r = { row: 0, col: 0, invDiff: 0 }; + +function _rowColfromParent(tile, zoom) { + const diffLevel = tile.zoom - zoom; + const diff = 2 ** diffLevel; + r.invDiff = 1 / diff; + + r.row = (tile.row - (tile.row % diff)) * r.invDiff; + r.col = (tile.col - (tile.col % diff)) * r.invDiff; + return r; +} + +const _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); +const _extent2 = new Extent('EPSG:4326', [0, 0, 0, 0]); + +const _c = new Coordinates('EPSG:4326', 0, 0); + +export const globalExtentTMS = new Map(); +export const schemeTiles = new Map(); + +const extent4326 = new Extent('EPSG:4326', -180, 180, -90, 90); +globalExtentTMS.set('EPSG:4326', extent4326); + +// Compute global extent of TMS in EPSG:3857 +// It's square whose a side is between -180° to 180°. +// So, west extent, it's 180 convert in EPSG:3857 +const extent3857 = extent4326.as('EPSG:3857'); +extent3857.clampSouthNorth(extent3857.west, extent3857.east); +globalExtentTMS.set('EPSG:3857', extent3857); + +schemeTiles.set('default', new THREE.Vector2(1, 1)); +schemeTiles.set(CRS.tms_3857, schemeTiles.get('default')); +schemeTiles.set(CRS.tms_4326, new THREE.Vector2(2, 1)); + +function getInfoTms(crs) { + const epsg = CRS.formatToEPSG(crs); + const globalExtent = globalExtentTMS.get(epsg); + const globalDimension = globalExtent.planarDimensions(_dim2); + const tms = CRS.formatToTms(crs); + const sTs = schemeTiles.get(tms) || schemeTiles.get('default'); + // The isInverted parameter is to be set to the correct value, true or false + // (default being false) if the computation of the coordinates needs to be + // inverted to match the same scheme as OSM, Google Maps or other system. + // See link below for more information + // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/ + // in crs includes ':NI' => tms isn't inverted (NOT INVERTED) + const isInverted = !tms.includes(':NI'); + return { epsg, globalExtent, globalDimension, sTs, isInverted }; +} + +function getCountTiles(crs, zoom) { + const sTs = schemeTiles.get(CRS.formatToTms(crs)) || schemeTiles.get('default'); + const count = 2 ** zoom; + _countTiles.set(count, count).multiply(sTs); + return _countTiles; +} + +class Tile { + /** + * Tile is a geographical bounding rectangle defined by zoom, row and column. + * + * @param {String} crs projection of limit values. + * @param {number} [zoom=0] zoom value + * @param {number} [row=0] row value + * @param {number} [col=0] column value + */ + constructor(crs, zoom = 0, row = 0, col = 0) { + this.isTile = true; + + this.crs = crs; + this.zoom = zoom; + this.row = row; + this.col = col; + } + + /** + * Clone this tile + * @return {Tile} cloned tile + */ + clone() { + return new Tile(this.crs, this.zoom, this.row, this.col); + } + + /** + * Convert tile to the specified extent. + * @param {string} crs the projection of destination. + * @param {Extent} target copy the destination to target. + * @return {Extent} + */ + toExtent(crs, target) { + CRS.isValid(crs); + target = target || new Extent('EPSG:4326', [0, 0, 0, 0]); + const { epsg, globalExtent, globalDimension } = getInfoTms(this.crs); + const countTiles = getCountTiles(this.crs, this.zoom); + + _dimensionTile.set(1, 1).divide(countTiles).multiply(globalDimension); + + target.west = globalExtent.west + (globalDimension.x - _dimensionTile.x * (countTiles.x - this.col)); + target.east = target.west + _dimensionTile.x; + target.south = globalExtent.south + _dimensionTile.y * (countTiles.y - this.row - 1); + target.north = target.south + _dimensionTile.y; + target.crs = epsg; + target.zoom = this.zoom; + + return crs == epsg ? target : target.as(crs, target); + } + + /** + * Return true if `tile` is inside this tile. + * + * @param {Tile} tile the tile to check + * + * @return {boolean} + */ + isInside(tile) { + if (this.zoom == tile.zoom) { + return this.row == tile.row && + this.col == tile.col; + } else if (this.zoom < tile.zoom) { + return false; + } else { + _rowColfromParent(this, tile.zoom); + return r.row == tile.row && r.col == tile.col; + } + } + + /** + * Return the translation and scale to transform this tile to input tile. + * + * @param {Tile} tile input tile + * @param {THREE.Vector4} target copy the result to target. + * @return {THREE.Vector4} {x: translation on west-east, y: translation on south-north, z: scale on west-east, w: scale on south-north} + */ + offsetToParent(tile, target = new THREE.Vector4()) { + if (this.crs != tile.crs) { + throw new Error('unsupported mix'); + } + + _rowColfromParent(this, tile.zoom); + return target.set( + this.col * r.invDiff - r.col, + this.row * r.invDiff - r.row, + r.invDiff, r.invDiff); + } + + /** + * Return parent tile with input level + * + * @param {number} levelParent level of parent. + * @return {Tile} + */ + tiledExtentParent(levelParent) { + if (levelParent && levelParent < this.zoom) { + _rowColfromParent(this, levelParent); + return new Tile(this.crs, levelParent, r.row, r.col); + } else { + return this; + } + } + + /** + * Set zoom, row and column values + * + * @param {number} [zoom=0] zoom value + * @param {number} [row=0] row value + * @param {number} [col=0] column value + * + * @return {Extent} + */ + set(zoom, row, col) { + this.zoom = zoom; + this.row = row; + this.col = col; + + return this; + } + + /** + * Copy to this tile to input tile. + * @param {Tile} tile + * @return {Tile} copied extent + */ + copy(tile) { + this.crs = tile.crs; + return this.set(tile.zoom, tile.row, tile.col); + } + + /** + * Return values of tile in string, separated by the separator input. + * @param {string} separator + * @return {string} + */ + toString(separator = '') { + return `${this.zoom}${separator}${this.row}${separator}${this.col}`; + } +} + +export function tiledCovering(e, tms) { + if (e.crs == 'EPSG:4326' && tms == CRS.tms_3857) { + const extents_WMTS_PM = []; + const extent = _extent.copy(e).as(CRS.formatToEPSG(tms), _extent2); + const { globalExtent, globalDimension, sTs } = getInfoTms(CRS.formatToEPSG(tms)); + extent.clampByExtent(globalExtent); + extent.planarDimensions(_dimensionTile); + + const zoom = (e.zoom + 1) || Math.floor(Math.log2(Math.round(globalDimension.x / (_dimensionTile.x * sTs.x)))); + const countTiles = getCountTiles(tms, zoom); + const center = extent.center(_c); + + _tmsCoord.x = center.x - globalExtent.west; + _tmsCoord.y = globalExtent.north - extent.north; + _tmsCoord.divide(globalDimension).multiply(countTiles).floor(); + + // ]N; N+1] => N + const maxRow = Math.ceil((globalExtent.north - extent.south) / globalDimension.x * countTiles.y) - 1; + + for (let r = maxRow; r >= _tmsCoord.y; r--) { + extents_WMTS_PM.push(new Tile(tms, zoom, r, _tmsCoord.x)); + } + + return extents_WMTS_PM; + } else { + const target = new Tile(tms, 0, 0, 0); + const { globalExtent, globalDimension, sTs, isInverted } = getInfoTms(e.crs); + const center = e.center(_c); + e.planarDimensions(_dimensionTile); + // Each level has 2^n * 2^n tiles... + // ... so we count how many tiles of the same width as tile we can fit in the layer + // ... 2^zoom = tilecount => zoom = log2(tilecount) + const zoom = Math.floor(Math.log2(Math.round(globalDimension.x / (_dimensionTile.x * sTs.x)))); + const countTiles = getCountTiles(tms, zoom); + + // Now that we have computed zoom, we can deduce x and y (or row / column) + _tmsCoord.x = center.x - globalExtent.west; + _tmsCoord.y = isInverted ? globalExtent.north - center.y : center.y - globalExtent.south; + _tmsCoord.divide(globalDimension).multiply(countTiles).floor(); + target.set(zoom, _tmsCoord.y, _tmsCoord.x); + return [target]; + } +} + +export default Tile; diff --git a/src/Core/Math/Ellipsoid.js b/src/Core/Math/Ellipsoid.ts similarity index 88% rename from src/Core/Math/Ellipsoid.js rename to src/Core/Math/Ellipsoid.ts index 8c0ea17221..7f7d9f36ee 100644 --- a/src/Core/Math/Ellipsoid.js +++ b/src/Core/Math/Ellipsoid.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; import proj4 from 'proj4'; -import Coordinates from 'Core/Geographic/Coordinates'; +import Coordinates from '../Geographic/Coordinates'; export const ellipsoidSizes = new THREE.Vector3( proj4.WGS84.a, @@ -10,7 +10,13 @@ export const ellipsoidSizes = new THREE.Vector3( const normal = new THREE.Vector3(); class Ellipsoid { - constructor(size = ellipsoidSizes) { + size: THREE.Vector3; + eccentricity: number; + + private _radiiSquared: THREE.Vector3; + private _invRadiiSquared: THREE.Vector3; + + constructor(size: THREE.Vector3 = ellipsoidSizes) { this.size = new THREE.Vector3(); this._radiiSquared = new THREE.Vector3(); this._invRadiiSquared = new THREE.Vector3(); @@ -19,11 +25,11 @@ class Ellipsoid { this.setSize(size); } - geodeticSurfaceNormal(cartesian, target = new THREE.Vector3()) { + geodeticSurfaceNormal(cartesian: Coordinates, target = new THREE.Vector3()) { return cartesian.toVector3(target).multiply(this._invRadiiSquared).normalize(); } - geodeticSurfaceNormalCartographic(coordCarto, target = new THREE.Vector3()) { + geodeticSurfaceNormalCartographic(coordCarto: Coordinates, target = new THREE.Vector3()) { const longitude = THREE.MathUtils.degToRad(coordCarto.longitude); const latitude = THREE.MathUtils.degToRad(coordCarto.latitude); const cosLatitude = Math.cos(latitude); @@ -33,7 +39,7 @@ class Ellipsoid { Math.sin(latitude)); } - setSize(size) { + setSize(size: THREE.Vector3Like) { this.size.set(size.x, size.y, size.z); this._radiiSquared.multiplyVectors(size, size); @@ -45,7 +51,7 @@ class Ellipsoid { this.eccentricity = Math.sqrt(this._radiiSquared.x - this._radiiSquared.z) / this.size.x; } - cartographicToCartesian(coordCarto, target = new THREE.Vector3()) { + cartographicToCartesian(coordCarto: Coordinates, target = new THREE.Vector3()) { normal.copy(coordCarto.geodesicNormal); target.multiplyVectors(this._radiiSquared, normal); @@ -68,7 +74,7 @@ class Ellipsoid { * @param {Coordinate} [target] coordinate to copy result * @returns {Coordinate} an object describing the coordinates on the reference ellipsoid, angles are in degree */ - cartesianToCartographic(position, target = new Coordinates('EPSG:4326', 0, 0, 0)) { + cartesianToCartographic(position: THREE.Vector3Like, target = new Coordinates('EPSG:4326', 0, 0, 0)) { // for details, see for example http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/equations-used-datum // TODO the following is only valable for oblate ellipsoid of revolution. do we want to support triaxial ellipsoid? const R = Math.sqrt(position.x * position.x + position.y * position.y + position.z * position.z); @@ -91,7 +97,7 @@ class Ellipsoid { return target.setFromValues(THREE.MathUtils.radToDeg(theta), THREE.MathUtils.radToDeg(phi), h); } - cartographicToCartesianArray(coordCartoArray) { + cartographicToCartesianArray(coordCartoArray: Coordinates[]) { const cartesianArray = []; for (let i = 0; i < coordCartoArray.length; i++) { cartesianArray.push(this.cartographicToCartesian(coordCartoArray[i])); @@ -100,7 +106,7 @@ class Ellipsoid { return cartesianArray; } - intersection(ray) { + intersection(ray: THREE.Ray) { const EPSILON = 0.0001; const O_C = ray.origin; const dir = ray.direction; @@ -137,11 +143,6 @@ class Ellipsoid { return inter; } - computeDistance(coordCarto1, coordCarto2) { - console.warn('computeDistance is renamed to geodesicDistance'); - this.geodesicDistance(coordCarto1, coordCarto2); - } - /** * Calculate the geodesic distance, between coordCarto1 and coordCarto2. * It's most short distance on ellipsoid surface between coordCarto1 and coordCarto2. @@ -151,7 +152,7 @@ class Ellipsoid { * @param {Coordinates} coordCarto2 The coordinate carto 2 * @return {number} The orthodromic distance between the two given coordinates. */ - geodesicDistance(coordCarto1, coordCarto2) { + geodesicDistance(coordCarto1: Coordinates, coordCarto2: Coordinates) { // The formula uses the distance on approximated sphere, // with the nearest local radius of curvature of the ellipsoid // https://geodesie.ign.fr/contenu/fichiers/Distance_longitude_latitude.pdf diff --git a/src/Core/Prefab/Globe/Atmosphere.js b/src/Core/Prefab/Globe/Atmosphere.js index 347b3a494d..3264fad5ab 100644 --- a/src/Core/Prefab/Globe/Atmosphere.js +++ b/src/Core/Prefab/Globe/Atmosphere.js @@ -145,8 +145,7 @@ class Atmosphere extends GeometryLayer { node.material.lightPosition = this.realisticLightingPosition; } - // eslint-disable-next-line no-unused-vars - preUpdate(context, srcs) { + preUpdate(context) { const cameraPosition = context.view.camera3D.position; if (this.fog.enable) { v.setFromMatrixPosition(context.view.tileLayer.object3d.matrixWorld); diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index 56cc2a5e3a..774e433c6c 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; import { ellipsoidSizes } from 'Core/Math/Ellipsoid'; -import { globalExtentTMS, schemeTiles } from 'Core/Geographic/Extent'; +import { globalExtentTMS, schemeTiles } from 'Core/Geographic/Tile'; import BuilderEllipsoidTile from 'Core/Prefab/Globe/BuilderEllipsoidTile'; import CRS from 'Core/Geographic/Crs'; diff --git a/src/Core/Prefab/Planar/PlanarLayer.js b/src/Core/Prefab/Planar/PlanarLayer.js index f9b26c09a5..6f23718d6d 100644 --- a/src/Core/Prefab/Planar/PlanarLayer.js +++ b/src/Core/Prefab/Planar/PlanarLayer.js @@ -1,7 +1,7 @@ import * as THREE from 'three'; import TiledGeometryLayer from 'Layer/TiledGeometryLayer'; -import { globalExtentTMS } from 'Core/Geographic/Extent'; +import { globalExtentTMS } from 'Core/Geographic/Tile'; import CRS from 'Core/Geographic/Crs'; import PlanarTileBuilder from './PlanarTileBuilder'; diff --git a/src/Core/TileMesh.js b/src/Core/TileMesh.js index d5983b9f0f..99fa28ec0f 100644 --- a/src/Core/TileMesh.js +++ b/src/Core/TileMesh.js @@ -1,6 +1,7 @@ import * as THREE from 'three'; import CRS from 'Core/Geographic/Crs'; import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; +import { tiledCovering } from 'Core/Geographic/Tile'; /** * A TileMesh is a THREE.Mesh with a geometricError and an OBB @@ -34,7 +35,7 @@ class TileMesh extends THREE.Mesh { this.obb.box3D.getBoundingSphere(this.boundingSphere); for (const tms of layer.tileMatrixSets) { - this.#_tms.set(tms, this.extent.tiledCovering(tms)); + this.#_tms.set(tms, tiledCovering(this.extent, tms)); } this.frustumCulled = false; diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index aa734ad7dd..9117a34783 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -4,8 +4,6 @@ import { init3dTilesLayer, pre3dTilesUpdate, process3dTilesNode } from 'Process/ import C3DTileset from 'Core/3DTiles/C3DTileset'; import C3DTExtensions from 'Core/3DTiles/C3DTExtensions'; import { PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE } from 'Renderer/PointsMaterial'; -// eslint-disable-next-line no-unused-vars -import Style from 'Core/Style'; import C3DTFeature from 'Core/3DTiles/C3DTFeature'; import { optimizeGeometryGroups } from 'Utils/ThreeUtils'; diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js index bd3a7bb2b4..321e6b7a2d 100644 --- a/src/Layer/LabelLayer.js +++ b/src/Layer/LabelLayer.js @@ -239,7 +239,11 @@ class LabelLayer extends GeometryLayer { const labels = []; // Converting the extent now is faster for further operation - extent.as(data.crs, _extent); + if (extent.isExtent) { + extent.as(data.crs, _extent); + } else { + extent.toExtent(data.crs, _extent); + } coord.crs = data.crs; context.setZoom(extent.zoom); diff --git a/src/Layer/OGC3DTilesLayer.js b/src/Layer/OGC3DTilesLayer.js index 1ef969c1e3..7a21c819fe 100644 --- a/src/Layer/OGC3DTilesLayer.js +++ b/src/Layer/OGC3DTilesLayer.js @@ -376,18 +376,18 @@ class OGC3DTilesLayer extends GeometryLayer { } // eslint-disable-next-line no-unused-vars - attach(layer) { + attach() { console.warn('[OGC3DTilesLayer]: Attaching / detaching layers is not yet implemented for OGC3DTilesLayer.'); } // eslint-disable-next-line no-unused-vars - detach(layer) { + detach() { console.warn('[OGC3DTilesLayer]: Attaching / detaching layers is not yet implemented for OGC3DTilesLayer.'); return true; } // eslint-disable-next-line no-unused-vars - getObjectToUpdateForAttachedLayers(obj) { + getObjectToUpdateForAttachedLayers() { return null; } diff --git a/src/Parser/GeoJsonParser.js b/src/Parser/GeoJsonParser.js index 3c31645c5b..09638a4577 100644 --- a/src/Parser/GeoJsonParser.js +++ b/src/Parser/GeoJsonParser.js @@ -217,7 +217,9 @@ export default { if (out.filteringExtent) { if (typeof out.filteringExtent == 'boolean') { - out.filterExtent = options.extent.as(_in.crs); + out.filterExtent = options.extent.isExtent ? + options.extent.as(_in.crs) : + options.extent.toExtent(_in.crs); } else if (out.filteringExtent.isExtent) { out.filterExtent = out.filteringExtent; } diff --git a/src/Parser/VectorTileParser.js b/src/Parser/VectorTileParser.js index 0a54d64b79..342b810543 100644 --- a/src/Parser/VectorTileParser.js +++ b/src/Parser/VectorTileParser.js @@ -1,7 +1,7 @@ import { Vector2, Vector3 } from 'three'; import Protobuf from 'pbf'; import { VectorTile } from '@mapbox/vector-tile'; -import { globalExtentTMS } from 'Core/Geographic/Extent'; +import { globalExtentTMS } from 'Core/Geographic/Tile'; import { FeatureCollection, FEATURE_TYPES } from 'Core/Feature'; import { deprecatedParsingOptionsToNewOne } from 'Core/Deprecated/Undeprecator'; import Coordinates from 'Core/Geographic/Coordinates'; diff --git a/src/Parser/iGLTFLoader.js b/src/Parser/iGLTFLoader.js index c6125430d0..7f7f43230c 100644 --- a/src/Parser/iGLTFLoader.js +++ b/src/Parser/iGLTFLoader.js @@ -32,8 +32,6 @@ class iGLTFLoader extends THREE.Loader { * @param {Function} onError */ load(url, onLoad, onProgress, onError) { - const scope = this; - let resourcePath; if (this.resourcePath !== '') { @@ -62,8 +60,8 @@ class iGLTFLoader extends THREE.Loader { console.error(e); } - scope.manager.itemError(url); - scope.manager.itemEnd(url); + this.manager.itemError(url); + this.manager.itemEnd(url); }; const loader = new THREE.FileLoader(this.manager); @@ -75,10 +73,10 @@ class iGLTFLoader extends THREE.Loader { loader.load(url, (data) => { try { - scope.parse(data, resourcePath, (gltf) => { + this.parse(data, resourcePath, (gltf) => { onLoad(gltf); - scope.manager.itemEnd(url); + this.manager.itemEnd(url); }, _onError); } catch (e) { _onError(e); @@ -171,10 +169,8 @@ class iGLTFLoader extends THREE.Loader { * .scene, .scenes, .cameras, .animations, and .asset, when parsing is done. */ parseAsync(data, path) { - const scope = this; - return new Promise((resolve, reject) => { - scope.parse(data, path, resolve, reject); + this.parse(data, path, resolve, reject); }); } } diff --git a/src/Provider/URLBuilder.js b/src/Provider/URLBuilder.js index 8caa459771..3eb2f97467 100644 --- a/src/Provider/URLBuilder.js +++ b/src/Provider/URLBuilder.js @@ -1,8 +1,9 @@ -import Extent from 'Core/Geographic/Extent'; - -const extent = new Extent('EPSG:4326', [0, 0, 0, 0]); - let subDomainsCount = 0; + +/** + * @param {string} url + * @returns {string} + */ function subDomains(url) { const subDomainsPtrn = /\$\{u:([\w-_.|]+)\}/.exec(url); @@ -57,15 +58,20 @@ export default { * // The resulting url is: * // http://server.geo/tms/15/2142/3412.jpg; * - * @param {Extent} coords - the coordinates - * @param {Source} source + * @param {Object} coords - tile coordinates + * @param {number} coords.row - tile row + * @param {number} coords.col - tile column + * @param {number} coords.zoom - tile zoom + * @param {Object} source + * @param {string} source.url + * @param {Function} source.tileMatrixCallback * * @return {string} the formed url */ xyz: function xyz(coords, source) { return subDomains(source.url.replace(/(\$\{z\}|%TILEMATRIX)/, source.tileMatrixCallback(coords.zoom)) - .replace(/(\$\{y\}|%ROW)/, coords.row) - .replace(/(\$\{x\}|%COL)/, coords.col)); + .replace(/(\$\{y\}|%ROW)/, coords.row.toString()) + .replace(/(\$\{x\}|%COL)/, coords.col.toString())); }, /** @@ -88,8 +94,12 @@ export default { * // The resulting url is: * // http://server.geo/wms/BBOX=12,35,14,46&FORMAT=jpg&SERVICE=WMS * - * @param {Extent} bbox - the bounding box - * @param {Object} source + * @param {Object} bbox - the bounding box + * @param {number} bbox.west + * @param {number} bbox.south + * @param {number} bbox.east + * @param {number} bbox.north + * @param {Object} source - the source of data * @param {string} source.crs * @param {number} source.bboxDigits * @param {string} source.url @@ -102,11 +112,10 @@ export default { if (source.bboxDigits !== undefined) { precision = source.bboxDigits; } - bbox.as(source.crs, extent); - const w = extent.west.toFixed(precision); - const s = extent.south.toFixed(precision); - const e = extent.east.toFixed(precision); - const n = extent.north.toFixed(precision); + const w = bbox.west.toFixed(precision); + const s = bbox.south.toFixed(precision); + const e = bbox.east.toFixed(precision); + const n = bbox.north.toFixed(precision); let bboxInUnit = source.axisOrder || 'wsen'; bboxInUnit = bboxInUnit.replace('w', `${w},`) diff --git a/src/Source/TMSSource.js b/src/Source/TMSSource.js index 3604ccb21b..7321d71af2 100644 --- a/src/Source/TMSSource.js +++ b/src/Source/TMSSource.js @@ -1,9 +1,10 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; -import Extent, { globalExtentTMS } from 'Core/Geographic/Extent'; +import Extent from 'Core/Geographic/Extent'; +import Tile, { globalExtentTMS } from 'Core/Geographic/Tile'; import CRS from 'Core/Geographic/Crs'; -const extent = new Extent(CRS.tms_4326, 0, 0, 0); +const _tile = new Tile(CRS.tms_4326, 0, 0, 0); /** * @classdesc @@ -118,8 +119,8 @@ class TMSSource extends Source { } } - urlFromExtent(extent) { - return URLBuilder.xyz(extent, this); + urlFromExtent(tile) { + return URLBuilder.xyz(tile, this); } onLayerAdded(options) { @@ -130,11 +131,11 @@ class TMSSource extends Source { const crs = parent ? parent.extent.crs : options.out.crs; if (this.tileMatrixSetLimits && !this.extentSetlimits[crs]) { this.extentSetlimits[crs] = {}; - extent.crs = this.crs; + _tile.crs = this.crs; for (let i = this.zoom.max; i >= this.zoom.min; i--) { const tmsl = this.tileMatrixSetLimits[i]; - const { west, north } = extent.set(i, tmsl.minTileRow, tmsl.minTileCol).as(crs); - const { east, south } = extent.set(i, tmsl.maxTileRow, tmsl.maxTileCol).as(crs); + const { west, north } = _tile.set(i, tmsl.minTileRow, tmsl.minTileCol).toExtent(crs); + const { east, south } = _tile.set(i, tmsl.maxTileRow, tmsl.maxTileCol).toExtent(crs); this.extentSetlimits[crs][i] = new Extent(crs, west, east, south, north); } } diff --git a/src/Source/VectorTilesSource.js b/src/Source/VectorTilesSource.js index 601bbc9d6e..6b5d86ed37 100644 --- a/src/Source/VectorTilesSource.js +++ b/src/Source/VectorTilesSource.js @@ -144,8 +144,8 @@ class VectorTilesSource extends TMSSource { }); } - urlFromExtent(extent, url) { - return URLBuilder.xyz(extent, { tileMatrixCallback: this.tileMatrixCallback, url }); + urlFromExtent(tile, url) { + return URLBuilder.xyz(tile, { tileMatrixCallback: this.tileMatrixCallback, url }); } onLayerAdded(options) { diff --git a/src/Source/WFSSource.js b/src/Source/WFSSource.js index 4413287432..79f5efc05f 100644 --- a/src/Source/WFSSource.js +++ b/src/Source/WFSSource.js @@ -1,6 +1,9 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; import CRS from 'Core/Geographic/Crs'; +import Extent from 'Core/Geographic/Extent'; + +const _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); /** * @classdesc @@ -169,7 +172,10 @@ class WFSSource extends Source { } } - urlFromExtent(extent) { + urlFromExtent(extentOrTile) { + const extent = extentOrTile.isExtent ? + extentOrTile.as(this.crs, _extent) : + extentOrTile.toExtent(this.crs, _extent); return URLBuilder.bbox(extent, this); } diff --git a/src/Source/WMSSource.js b/src/Source/WMSSource.js index 2f8664ab1b..9fd24f9a70 100644 --- a/src/Source/WMSSource.js +++ b/src/Source/WMSSource.js @@ -1,5 +1,8 @@ import Source from 'Source/Source'; import URLBuilder from 'Provider/URLBuilder'; +import Extent from 'Core/Geographic/Extent'; + +const _extent = new Extent('EPSG:4326', [0, 0, 0, 0]); /** * @classdesc @@ -138,7 +141,10 @@ class WMSSource extends Source { } } - urlFromExtent(extent) { + urlFromExtent(extentOrTile) { + const extent = extentOrTile.isExtent ? + extentOrTile.as(this.crs, _extent) : + extentOrTile.toExtent(this.crs, _extent); return URLBuilder.bbox(extent, this); } diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index 59562924da..dac39c11e7 100644 --- a/test/unit/dataSourceProvider.js +++ b/test/unit/dataSourceProvider.js @@ -3,7 +3,8 @@ import assert from 'assert'; import { updateLayeredMaterialNodeImagery, updateLayeredMaterialNodeElevation } from 'Process/LayeredMaterialNodeProcessing'; import FeatureProcessing from 'Process/FeatureProcessing'; import TileMesh from 'Core/TileMesh'; -import Extent, { globalExtentTMS } from 'Core/Geographic/Extent'; +import Extent from 'Core/Geographic/Extent'; +import { globalExtentTMS } from 'Core/Geographic/Tile'; import OBB from 'Renderer/OBB'; import DataSourceProvider from 'Provider/DataSourceProvider'; import Fetcher from 'Provider/Fetcher'; diff --git a/test/unit/extent.js b/test/unit/extent.js index c95d7fe2a4..1a36897670 100644 --- a/test/unit/extent.js +++ b/test/unit/extent.js @@ -2,6 +2,7 @@ import assert from 'assert'; import { Box3, Vector3, Vector2, Matrix4, Quaternion } from 'three'; import Coordinates from 'Core/Geographic/Coordinates'; import Extent from 'Core/Geographic/Extent'; +import Tile from 'Core/Geographic/Tile'; import CRS from 'Core/Geographic/Crs'; import proj4 from 'proj4'; @@ -144,20 +145,20 @@ describe('Extent', function () { }); it('should clone tiled extent like expected', function () { - const withValues = new Extent('TMS:4326', zoom, row, col); - const clonedExtent = withValues.clone(); - assert.equal(clonedExtent.zoom, withValues.zoom); - assert.equal(clonedExtent.row, withValues.row); - assert.equal(clonedExtent.col, withValues.col); + const withValues = new Tile('TMS:4326', zoom, row, col); + const clonedTile = withValues.clone(); + assert.equal(clonedTile.zoom, withValues.zoom); + assert.equal(clonedTile.row, withValues.row); + assert.equal(clonedTile.col, withValues.col); }); it('should isTms return true if extent is tiled extent', function () { - const withValues = new Extent('TMS:4326', zoom, row, col); + const withValues = new Tile('TMS:4326', zoom, row, col); assert.ok(CRS.isTms(withValues.crs)); }); it('should convert extent TMS:4326 like expected', function () { - const withValues = new Extent('TMS:4326', 0, 0, 0).as('EPSG:4326'); + const withValues = new Tile('TMS:4326', 0, 0, 0).toExtent('EPSG:4326'); assert.equal(-180, withValues.west); assert.equal(0, withValues.east); assert.equal(-90, withValues.south); @@ -165,7 +166,7 @@ describe('Extent', function () { }); it('should convert extent TMS:3857 to EPSG:3857 like expected', function () { - const withValues = new Extent('TMS:3857', 0, 0, 0).as('EPSG:3857'); + const withValues = new Tile('TMS:3857', 0, 0, 0).toExtent('EPSG:3857'); assert.equal(-20037508.342789244, withValues.west); assert.equal(20037508.342789244, withValues.east); assert.equal(-20037508.342789244, withValues.south); @@ -173,8 +174,8 @@ describe('Extent', function () { }); it('should convert extent TMS:3857 to EPSG:4326 like expected', function () { - const withValues = new Extent('TMS:3857', 0, 0, 0); - const result = withValues.as('EPSG:4326'); + const withValues = new Tile('TMS:3857', 0, 0, 0); + const result = withValues.toExtent('EPSG:4326'); assert.equal(-180.00000000000003, result.west); assert.equal(180.00000000000003, result.east); assert.equal(-85.0511287798066, result.south); @@ -225,8 +226,8 @@ describe('Extent', function () { }); it('should return expected offset using tiled extent', function () { - const withValues = new Extent('TMS:4326', zoom, row, col); - const parent = new Extent('TMS:4326', zoom - 2, row, col); + const withValues = new Tile('TMS:4326', zoom, row, col); + const parent = new Tile('TMS:4326', zoom - 2, row, col); const offset = withValues.offsetToParent(parent); assert.equal(offset.x, 0.5); assert.equal(offset.y, 0.5); @@ -235,7 +236,7 @@ describe('Extent', function () { }); it('should return expected tiled extent parent', function () { - const withValues = new Extent('TMS:4326', zoom, row, col); + const withValues = new Tile('TMS:4326', zoom, row, col); const parent = withValues.tiledExtentParent(zoom - 2); assert.equal(parent.zoom, 3); assert.equal(parent.row, 5); @@ -308,7 +309,7 @@ describe('Extent', function () { }); it('should convert TMS extent values to string', function () { - const withValues = new Extent('TMS:4326', 0, 1, 2); + const withValues = new Tile('TMS:4326', 0, 1, 2); const tostring = withValues.toString(','); const toValues = tostring.split(',').map(s => Number(s)); assert.equal(toValues[0], withValues.zoom); diff --git a/test/unit/featuregeometrylayererror.js b/test/unit/featuregeometrylayererror.js deleted file mode 100644 index 09d696d370..0000000000 --- a/test/unit/featuregeometrylayererror.js +++ /dev/null @@ -1,121 +0,0 @@ -import * as THREE from 'three'; -import assert from 'assert'; -import GlobeView from 'Core/Prefab/GlobeView'; -import FeatureGeometryLayer from 'Layer/FeatureGeometryLayer'; -import FileSource from 'Source/FileSource'; -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'; - -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]; -const sizes = [70, 600, 20000]; - -files.forEach((geojson, i) => { - describe(`Feature2Mesh, difference between proj4 and without proj4, for ${sizes[i]} meters extent dimension `, function () { - const renderer = new Renderer(); - const max_error = errors[i]; - - const placement = { coord: new Coordinates('EPSG:4326', 4.2, 48.2), range: 2000 }; - const viewer = new GlobeView(renderer.domElement, placement, { renderer }); - - const source = new FileSource({ - fetchedData: geojson, - crs: 'EPSG:4326', - format: 'application/json', - }); - - const source2 = new FileSource(source); - - const layerProj4 = new FeatureGeometryLayer('proj4', { - source, - accurate: true, - zoom: { min: 9 }, - }); - - const layerNoProj4 = new FeatureGeometryLayer('layerNoProj4', { - source: source2, - accurate: false, - zoom: { min: 9 }, - }); - - const context = { - camera: viewer.camera, - engine: viewer.mainLoop.gfxEngine, - scheduler: viewer.mainLoop.scheduler, - geometryLayer: layerProj4, - view: viewer, - }; - - 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(), viewer.tileLayer, extent, 9); - tile.parent = {}; - - viewer.addLayer(layerProj4); - viewer.addLayer(layerNoProj4); - - it('update proj4', function (done) { - layerProj4.whenReady - .then(() => { - tile.visible = true; - return layerProj4.update(context, layerProj4, tile); - }) - .then(() => { - assert.equal(layerProj4.object3d.children.length, 1); - done(); - }).catch(done); - }); - - it('update without proj4', function (done) { - layerNoProj4.whenReady - .then(() => { - tile.visible = true; - return layerNoProj4.update(context, layerNoProj4, tile); - }) - .then(() => { - assert.equal(layerNoProj4.object3d.children.length, 1); - done(); - }).catch(done); - }); - - it(`parsing error without proj4 should be inferior to ${max_error} meter`, function (done) { - Promise.all([layerNoProj4.whenReady, layerProj4.whenReady]) - .then(() => { - const meshNoProj4 = layerNoProj4.object3d.children[0].meshes.children[0]; - const mesh = layerProj4.object3d.children[0].meshes.children[0]; - const array = mesh.geometry.attributes.position.array; - const arrayNoProj4 = meshNoProj4.geometry.attributes.position.array; - const vectorNoProj4 = new THREE.Vector3(); - const vectorProj4 = new THREE.Vector3(); - let error = 0; - - for (let i = array.length - 3; i >= 0; i -= 3) { - // transform proj4 vertex to final projection - vectorProj4.fromArray(array, i); - vectorProj4.applyMatrix4(mesh.matrixWorld); - - // transform proj4 vertex to final projection - vectorNoProj4.fromArray(arrayNoProj4, i); - vectorNoProj4.applyMatrix4(meshNoProj4.matrixWorld); - - // compute diff between proj4 vertex and no proj4 vertex - const distance = vectorProj4.distanceTo(vectorNoProj4); - error += distance; - } - - error /= (array.length / 3); - - assert.ok(error < max_error, `error (${error}) sup. to ${max_error}`); - done(); - }).catch(done); - }); - }); -}); diff --git a/test/unit/layeredmaterial.js b/test/unit/layeredmaterial.js index 0e05be62d1..0a58fefd76 100644 --- a/test/unit/layeredmaterial.js +++ b/test/unit/layeredmaterial.js @@ -6,7 +6,7 @@ import GlobeView from 'Core/Prefab/GlobeView'; import Coordinates from 'Core/Geographic/Coordinates'; import TileMesh from 'Core/TileMesh'; import * as THREE from 'three'; -import Extent from 'Core/Geographic/Extent'; +import Tile from 'Core/Geographic/Tile'; import OBB from 'Renderer/OBB'; import LayeredMaterial from 'Renderer/LayeredMaterial'; import sinon from 'sinon'; @@ -35,7 +35,7 @@ describe('material state vs layer state', function () { view.tileLayer.colorLayersOrder = [layer.id]; view.addLayer(layer); - const extent = new Extent('TMS:4326', 3, 0, 0).as('EPSG:4326'); + const extent = new Tile('TMS:4326', 3, 0, 0).toExtent('EPSG:4326'); material = new LayeredMaterial(); const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); diff --git a/test/unit/provider_url.js b/test/unit/provider_url.js index 24e0173c68..0421cacd83 100644 --- a/test/unit/provider_url.js +++ b/test/unit/provider_url.js @@ -2,19 +2,20 @@ import assert from 'assert'; import URLBuilder from 'Provider/URLBuilder'; import Extent from 'Core/Geographic/Extent'; +import Tile from 'Core/Geographic/Tile'; describe('URL creations', function () { const layer = { tileMatrixCallback: (zoomLevel => zoomLevel) }; it('should correctly replace ${x}, ${y} and ${z} by 359, 512 and 10', function () { - const coords = new Extent('TMS:4857', 10, 512, 359); + const coords = new Tile('TMS:4857', 10, 512, 359); layer.url = 'http://server.geo/tms/${z}/${y}/${x}.jpg'; const result = URLBuilder.xyz(coords, layer); assert.equal(result, 'http://server.geo/tms/10/512/359.jpg'); }); it('should correctly replace %COL, %ROW, %TILEMATRIX by 2072, 1410 and 12', function () { - const coords = new Extent('TMS:4326', 12, 1410, 2072); + const coords = new Tile('TMS:4326', 12, 1410, 2072); layer.url = 'http://server.geo/wmts/SERVICE=WMTS&TILEMATRIX=%TILEMATRIX&TILEROW=%ROW&TILECOL=%COL'; const result = URLBuilder.xyz(coords, layer); assert.equal(result, 'http://server.geo/wmts/SERVICE=WMTS&TILEMATRIX=12&TILEROW=1410&TILECOL=2072'); @@ -50,13 +51,13 @@ describe('URL creations', function () { it('should correctly replace sub-domains pattern', function () { layer.url = 'https://${u:xyz.org|yzx.org|zxy.org}/img.png'; - assert.equal(URLBuilder.xyz({}, layer), 'https://xyz.org/img.png'); - assert.equal(URLBuilder.xyz({}, layer), 'https://yzx.org/img.png'); - assert.equal(URLBuilder.xyz({}, layer), 'https://zxy.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://xyz.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://yzx.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://zxy.org/img.png'); layer.url = 'https://${u:a|b|c}.tile.openstreetmap.org/img.png'; - assert.equal(URLBuilder.xyz({}, layer), 'https://a.tile.openstreetmap.org/img.png'); - assert.equal(URLBuilder.xyz({}, layer), 'https://b.tile.openstreetmap.org/img.png'); - assert.equal(URLBuilder.xyz({}, layer), 'https://c.tile.openstreetmap.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://a.tile.openstreetmap.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://b.tile.openstreetmap.org/img.png'); + assert.equal(URLBuilder.subDomains(layer.url), 'https://c.tile.openstreetmap.org/img.png'); }); }); diff --git a/test/unit/source.js b/test/unit/source.js index 54dd2824a6..eab53a81df 100644 --- a/test/unit/source.js +++ b/test/unit/source.js @@ -11,6 +11,7 @@ import OrientedImageSource from 'Source/OrientedImageSource'; import C3DTilesSource from 'Source/C3DTilesSource'; import C3DTilesIonSource from 'Source/C3DTilesIonSource'; import Extent from 'Core/Geographic/Extent'; +import Tile from 'Core/Geographic/Tile'; import sinon from 'sinon'; import Fetcher from 'Provider/Fetcher'; @@ -78,11 +79,11 @@ describe('Sources', function () { it('should return keys from request', function () { const source = new WFSSource(paramsWFS); - const extent = new Extent('TMS:4326', 5, 10, 15); - const keys = source.requestToKey(extent); - assert.equal(extent.zoom, keys[0]); - assert.equal(extent.row, keys[1]); - assert.equal(extent.col, keys[2]); + const tile = new Tile('TMS:4326', 5, 10, 15); + const keys = source.requestToKey(tile); + assert.equal(tile.zoom, keys[0]); + assert.equal(tile.row, keys[1]); + assert.equal(tile.col, keys[2]); const extentepsg = new Extent('EPSG:4326', 5.5, 10, 22.3, 89.34); const keysepsg = source.requestToKey(extentepsg); assert.equal(extentepsg.zoom, 0); @@ -105,7 +106,7 @@ describe('Sources', function () { it('should instance and use WMTSSource', function () { const source = new WMTSSource(paramsWMTS); - const extent = new Extent('TMS:3857', 5, 0, 0); + const extent = new Tile('TMS:3857', 5, 0, 0); assert.ok(source.isWMTSSource); assert.ok(source.urlFromExtent(extent)); assert.ok(source.extentInsideLimit(extent, 5)); @@ -122,7 +123,7 @@ describe('Sources', function () { 5: { minTileRow: 0, maxTileRow: 32, minTileCol: 0, maxTileCol: 32 }, }; const source = new WMTSSource(paramsWMTS); - const extent = new Extent('TMS:3857', 5, 0, 0).as('EPSG:4326'); + const extent = new Tile('TMS:3857', 5, 0, 0); source.onLayerAdded({ out: { crs: 'EPSG:4326' } }); assert.ok(source.isWMTSSource); assert.ok(source.urlFromExtent(extent)); @@ -132,8 +133,8 @@ describe('Sources', function () { it('should use vendor specific parameters for the creation of the WMTS url', function () { paramsWMTS.vendorSpecific = vendorSpecific; const source = new WMTSSource(paramsWMTS); - const extent = new Extent('EPSG:4326', 0, 10, 0, 10); - const url = source.urlFromExtent(extent); + const tile = new Tile('TMS:4326', 0, 10, 0); + const url = source.urlFromExtent(tile); const end = '&buffer=4096&format_options=dpi:300;quantizer:octree&tiled=true'; assert.ok(url.endsWith(end)); }); @@ -203,7 +204,7 @@ describe('Sources', function () { it('should instance and use TMSSource', function () { const source = new TMSSource(paramsTMS); source.onLayerAdded({ out: { crs: 'EPSG:4326' } }); - const extent = new Extent('TMS:3857', 5, 0, 0); + const extent = new Tile('TMS:3857', 5, 0, 0); assert.ok(source.isTMSSource); assert.ok(source.urlFromExtent(extent)); assert.ok(source.extentInsideLimit(extent, extent.zoom)); diff --git a/test/unit/tilemesh.js b/test/unit/tilemesh.js index 0d6b211f6d..53cdbb6e25 100644 --- a/test/unit/tilemesh.js +++ b/test/unit/tilemesh.js @@ -3,7 +3,7 @@ import assert from 'assert'; import TileMesh from 'Core/TileMesh'; // import PlanarView from 'Core/Prefab/PlanarView'; import PlanarLayer from 'Core/Prefab/Planar/PlanarLayer'; -import Extent, { globalExtentTMS } from 'Core/Geographic/Extent'; +import Tile, { globalExtentTMS } from 'Core/Geographic/Tile'; import TileProvider from 'Provider/TileProvider'; import newTileGeometry from 'Core/Prefab/TileBuilder'; import OBB from 'Renderer/OBB'; @@ -22,7 +22,7 @@ FakeTileMesh.prototype.constructor = FakeTileMesh; FakeTileMesh.prototype.findCommonAncestor = TileMesh.prototype.findCommonAncestor; describe('TileMesh', function () { - const extent = new Extent('TMS:3857', 5, 10, 10); + const tile = new Tile('TMS:3857', 5, 10, 10); const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(); @@ -161,7 +161,7 @@ describe('TileMesh', function () { it('throw error if there\'s not extent in constructor', () => { assert.doesNotThrow(() => { // eslint-disable-next-line no-unused-vars - const tileMesh = new TileMesh(geom, new THREE.Material(), planarlayer, extent.as('EPSG:3857'), 0); + const tileMesh = new TileMesh(geom, new THREE.Material(), planarlayer, tile.toExtent('EPSG:3857'), 0); }); assert.throws(() => { // eslint-disable-next-line no-unused-vars @@ -178,7 +178,7 @@ describe('TileMesh', function () { material.setSequenceElevation = () => {}; it('event rasterElevationLevelChanged RasterElevationTile sets TileMesh bounding box ', () => { - const tileMesh = new TileMesh(geom, material, planarlayer, extent.as('EPSG:3857'), 0); + const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); const rasterNode = elevationLayer.setupRasterNode(tileMesh); const min = 50; const max = 500; @@ -197,7 +197,7 @@ describe('TileMesh', function () { it('RasterElevationTile throws error if ElevationLayer.useRgbaTextureElevation is true', () => { elevationLayer.useRgbaTextureElevation = true; - const tileMesh = new TileMesh(geom, material, planarlayer, extent.as('EPSG:3857'), 0); + const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); assert.throws(() => { elevationLayer.setupRasterNode(tileMesh); }); @@ -209,7 +209,7 @@ describe('TileMesh', function () { elevationLayer.useColorTextureElevation = true; elevationLayer.colorTextureElevationMinZ = 10; elevationLayer.colorTextureElevationMaxZ = 100; - const tileMesh = new TileMesh(geom, material, planarlayer, extent.as('EPSG:3857'), 0); + const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); const rasterNode = elevationLayer.setupRasterNode(tileMesh); assert.equal(rasterNode.min, elevationLayer.colorTextureElevationMinZ); assert.equal(rasterNode.max, elevationLayer.colorTextureElevationMaxZ); @@ -217,10 +217,10 @@ describe('TileMesh', function () { it('RasterElevationTile min and max are set by xbil texture', () => { delete elevationLayer.useColorTextureElevation; - const tileMesh = new TileMesh(geom, material, planarlayer, extent.as('EPSG:3857'), 0); + const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); const rasterNode = elevationLayer.setupRasterNode(tileMesh); const texture = new THREE.Texture(); - texture.extent = new Extent('TMS:3857', 4, 10, 10); + texture.extent = new Tile('TMS:3857', 4, 10, 10); texture.image = { width: 3, height: 3, diff --git a/test/unit/vectortiles.js b/test/unit/vectortiles.js index 46912bd648..c179e6f5d7 100644 --- a/test/unit/vectortiles.js +++ b/test/unit/vectortiles.js @@ -2,7 +2,7 @@ import fs from 'fs'; import assert from 'assert'; import VectorTileParser from 'Parser/VectorTileParser'; import VectorTilesSource from 'Source/VectorTilesSource'; -import Extent from 'Core/Geographic/Extent'; +import Tile from 'Core/Geographic/Tile'; import urlParser from 'Parser/MapBoxUrlParser'; import Fetcher from 'Provider/Fetcher'; import sinon from 'sinon'; @@ -23,7 +23,7 @@ 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'); - const extent = new Extent('TMS', 1, 1, 1); + const tile = new Tile('TMS', 1, 1, 1); function parse(pbf, layers) { return VectorTileParser.parse(pbf, { @@ -34,7 +34,7 @@ describe('Vector tiles', function () { out: { crs: 'EPSG:3857', }, - extent, + extent: tile, }); } @@ -367,8 +367,8 @@ describe('VectorTilesSource', function () { source.whenReady .then(() => { source.onLayerAdded({ out: { crs: 'EPSG:4326' } }); - const extent = new Extent('TMS', 1, 1, 1); - source.loadData(extent, { crs: 'EPSG:4326' }) + const tile = new Tile('TMS', 1, 1, 1); + source.loadData(tile, { crs: 'EPSG:4326' }) .then((featureCollection) => { assert.equal(featureCollection.features[0].vertices.length, 20); done(); diff --git a/tsconfig.json b/tsconfig.json index 63841f2d26..af0296a022 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,7 @@ /* Type Checking */ "strict": true, /* Modules */ - "baseUrl": ".", - "paths": { - "*": [ "src/*" ] - }, + "baseUrl": "./src", "module": "ESNext", /* Emit */ "declaration": true, diff --git a/webpack.config.cjs b/webpack.config.cjs index 5cfa18f1ef..d954fb78c0 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -45,6 +45,12 @@ module.exports = () => { return { mode, context: path.resolve(__dirname), + resolve: { + extensions: ['.ts', '.js'], + extensionAlias: { + '.js': ['.ts', '.js'], + }, + }, entry: { itowns: [ 'core-js', @@ -76,7 +82,7 @@ module.exports = () => { module: { rules: [ { - test: /\.js$/, + test: /\.(js|ts)$/, exclude, include, use: babelLoaderOptions,