diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 04b849c7..6ba4c8e2 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -23,7 +23,8 @@ jobs: - run: | mkdir public cp src/js/data-layer/com_wiki.json public/com_wiki.json - cp src/js/data-layer/immersvie-position-config.json public/immersvie-position-config.json + cp src/js/data-layer/immersive-position-config.json public/immersive-position-config.json + cp src/js/data-layer/immersive-position-config.json public/immersvie-position-config.json cp src/js/data-layer/code_cultu-caption.json public/code_cultu-caption.json cp src/js/data-layer/code_tfv-caption.json public/code_tfv-caption.json cp src/js/data-layer/base-layer-config.json public/base-layer-config.json diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 74dd639e..b2c751a3 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 07fcadac..69372600 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -10,6 +10,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':aashu-dubey-capacitor-statusbar-safe-area') + implementation project(':capacitor-community-background-geolocation') implementation project(':capacitor-community-keep-awake') implementation project(':capacitor-app') implementation project(':capacitor-clipboard') @@ -17,6 +18,7 @@ dependencies { implementation project(':capacitor-filesystem') implementation project(':capacitor-geolocation') implementation project(':capacitor-keyboard') + implementation project(':capacitor-local-notifications') implementation project(':capacitor-network') implementation project(':capacitor-preferences') implementation project(':capacitor-screen-orientation') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2490f2be..1f9f7fe2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -105,4 +105,6 @@ + + diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index f68133b8..57493843 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/ include ':aashu-dubey-capacitor-statusbar-safe-area' project(':aashu-dubey-capacitor-statusbar-safe-area').projectDir = new File('../node_modules/@aashu-dubey/capacitor-statusbar-safe-area/android') +include ':capacitor-community-background-geolocation' +project(':capacitor-community-background-geolocation').projectDir = new File('../node_modules/@capacitor-community/background-geolocation/android') + include ':capacitor-community-keep-awake' project(':capacitor-community-keep-awake').projectDir = new File('../node_modules/@capacitor-community/keep-awake/android') @@ -26,6 +29,9 @@ project(':capacitor-geolocation').projectDir = new File('../node_modules/@capaci include ':capacitor-keyboard' project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android') +include ':capacitor-local-notifications' +project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android') + include ':capacitor-network' project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android') diff --git a/capacitor.config.json b/capacitor.config.json index d8be35d1..effd455e 100644 --- a/capacitor.config.json +++ b/capacitor.config.json @@ -25,6 +25,9 @@ "spinnerColor": "#000000", "splashFullScreen": false, "splashImmersive": false + }, + "CapacitorHttp": { + "enabled": true } } } diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index 033e2dcb..8e2941f2 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -48,6 +48,12 @@ ITSAppUsesNonExemptEncryption NSLocationWhenInUseUsageDescription Votre position est nécessaire pour vous positionner sur la carte et calculer des itinéraires et isochrones à partir de votre position + NSLocationAlwaysAndWhenInUseUsageDescription + Si vous souhaitez recevoir des notifications lorsque vous arrivez dans un nouveau lieu, votre position en arrière-plan est nécessaire + UIBackgroundModes + + location + UIFileSharingEnabled LSSupportsOpeningDocumentsInPlace diff --git a/ios/App/Podfile b/ios/App/Podfile index 6e218f32..d1554cd8 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -12,6 +12,7 @@ def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'AashuDubeyCapacitorStatusbarSafeArea', :path => '../../node_modules/@aashu-dubey/capacitor-statusbar-safe-area' + pod 'CapacitorCommunityBackgroundGeolocation', :path => '../../node_modules/@capacitor-community/background-geolocation' pod 'CapacitorCommunityKeepAwake', :path => '../../node_modules/@capacitor-community/keep-awake' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard' @@ -19,6 +20,7 @@ def capacitor_pods pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem' pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation' pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' + pod 'CapacitorLocalNotifications', :path => '../../node_modules/@capacitor/local-notifications' pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network' pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences' pod 'CapacitorScreenOrientation', :path => '../../node_modules/@capacitor/screen-orientation' diff --git a/package-lock.json b/package-lock.json index ed357fd9..12befea5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GPLv3", "dependencies": { "@aashu-dubey/capacitor-statusbar-safe-area": "^4.0.0", + "@capacitor-community/background-geolocation": "^1.2.22", "@capacitor-community/keep-awake": "^7.0.0", "@capacitor/android": "^7.0.0", "@capacitor/app": "^7.0.0", @@ -20,6 +21,7 @@ "@capacitor/geolocation": "^7.0.0", "@capacitor/ios": "^7.0.0", "@capacitor/keyboard": "^7.0.0", + "@capacitor/local-notifications": "^7.0.0", "@capacitor/network": "^7.0.0", "@capacitor/preferences": "^7.0.0", "@capacitor/screen-orientation": "^7.0.0", @@ -39,6 +41,7 @@ "@turf/clean-coords": "^7.2.0", "@turf/length": "^7.2.0", "@turf/line-slice": "^7.2.0", + "@turf/point-to-line-distance": "^7.2.0", "@turf/union": "^7.2.0", "@xmldom/xmldom": "^0.8.10", "capacitor-native-settings": "^7.0.1", @@ -1598,6 +1601,15 @@ "node": ">=6.9.0" } }, + "node_modules/@capacitor-community/background-geolocation": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@capacitor-community/background-geolocation/-/background-geolocation-1.2.22.tgz", + "integrity": "sha512-SiEV3wVb68ESV5858EKDYSjULUE9cdkSxB9E+19NwI0Td+sfTu/rDnZuVb1mu5ydoMrkmgYefbmc2R/0KYgubg==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=3.0.0" + } + }, "node_modules/@capacitor-community/keep-awake": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@capacitor-community/keep-awake/-/keep-awake-7.0.0.tgz", @@ -2048,6 +2060,15 @@ "@capacitor/core": ">=7.0.0" } }, + "node_modules/@capacitor/local-notifications": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@capacitor/local-notifications/-/local-notifications-7.0.1.tgz", + "integrity": "sha512-GJewoiqiTLXNNRxqeJDi6vxj1Y37jLFI3KSdAM2Omvxew4ewyBSCjwOtXMQaEg+lvzGHtK6FPrSc2v/2EcL0wA==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=7.0.0" + } + }, "node_modules/@capacitor/network": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@capacitor/network/-/network-7.0.1.tgz", @@ -3398,174 +3419,6 @@ "@parcel/watcher-win32-x64": "2.5.1" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher-linux-x64-glibc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", @@ -3587,90 +3440,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4051,6 +3820,27 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@turf/bearing": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-7.2.0.tgz", + "integrity": "sha512-Jm0Xt3GgHjRrWvBtAGvgfnADLm+4exud2pRlmCYx8zfiKuNXQFkrcTZcOiJOgTfG20Agq28iSh15uta47jSIbg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@turf/buffer": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.2.0.tgz", @@ -4274,6 +4064,34 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@turf/point-to-line-distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-7.2.0.tgz", + "integrity": "sha512-fB9Rdnb5w5+t76Gho2dYDkGe20eRrFk8CXi4v1+l1PC8YyLXO+x+l3TrtT8HzL/dVaZeepO6WUIsIw3ditTOPg==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "^7.2.0", + "@turf/distance": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/nearest-point-on-line": "^7.2.0", + "@turf/projection": "^7.2.0", + "@turf/rhumb-bearing": "^7.2.0", + "@turf/rhumb-distance": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@turf/projection": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.2.0.tgz", @@ -4296,6 +4114,48 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@turf/rhumb-bearing": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-7.2.0.tgz", + "integrity": "sha512-jbdexlrR8X2ZauUciHx3tRwG+BXoMXke4B8p8/IgDlAfIrVdzAxSQN89FMzIKnjJ/kdLjo9bFGvb92bu31Etug==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@turf/rhumb-distance": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-7.2.0.tgz", + "integrity": "sha512-NsijTPON1yOc9tirRPEQQuJ5aQi7pREsqchQquaYKbHNWsexZjcDi4wnw2kM3Si4XjmgynT+2f7aXH7FHarHzw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@turf/union": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/union/-/union-7.2.0.tgz", @@ -8374,21 +8234,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/package.json b/package.json index c0daa659..c745f811 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "license": "GPLv3", "dependencies": { "@aashu-dubey/capacitor-statusbar-safe-area": "^4.0.0", + "@capacitor-community/background-geolocation": "^1.2.22", "@capacitor-community/keep-awake": "^7.0.0", "@capacitor/android": "^7.0.0", "@capacitor/app": "^7.0.0", @@ -40,6 +41,7 @@ "@capacitor/geolocation": "^7.0.0", "@capacitor/ios": "^7.0.0", "@capacitor/keyboard": "^7.0.0", + "@capacitor/local-notifications": "^7.0.0", "@capacitor/network": "^7.0.0", "@capacitor/preferences": "^7.0.0", "@capacitor/screen-orientation": "^7.0.0", @@ -60,6 +62,7 @@ "@turf/length": "^7.2.0", "@turf/line-slice": "^7.2.0", "@turf/union": "^7.2.0", + "@turf/point-to-line-distance": "^7.2.0", "@xmldom/xmldom": "^0.8.10", "capacitor-native-settings": "^7.0.1", "chart.js": "^4.4.1", diff --git a/src/js/controls.js b/src/js/controls.js index 5f9d516d..e0f000e8 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -26,6 +26,8 @@ import OfflineMaps from "./offline-maps"; import DOM from "./dom"; import LocationLayers from "./services/location-styles"; +// TODO: activate +// import ImmersiveNotifications from "./immersive-notifications"; /** * Ajout des contrôle à la fin du chargement de la carte @@ -132,6 +134,10 @@ const addControls = () => { } }); + // TODO: activate + // // contrôle "notifications immersives" + // Globals.immersiveNotifications = new ImmersiveNotifications(true); + // compte utilisateur Globals.myaccount = new MyAccount(map, {}); diff --git a/src/js/data-layer/immersvie-position-config.json b/src/js/data-layer/immersive-position-config.json similarity index 87% rename from src/js/data-layer/immersvie-position-config.json rename to src/js/data-layer/immersive-position-config.json index 29ce8881..fbe75d38 100644 --- a/src/js/data-layer/immersvie-position-config.json +++ b/src/js/data-layer/immersive-position-config.json @@ -21,28 +21,23 @@ "id": "BDTOPO_V3:foret_publique", "layer": "BDTOPO_V3:foret_publique", "attributes": ["toponyme"], - "geom_name": "geometrie" - }, - { - "id": "BDTOPO_V3:toponymie_lieux_nommes", - "layer": "BDTOPO_V3:toponymie_lieux_nommes", - "attributes": ["graphie_du_toponyme"], "geom_name": "geometrie", - "around": 1, - "additional_cql": "AND nature_de_l_objet='Bois'" + "notification_uses_next": true }, { - "id": "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale", + "id": "Essence intesection", "layer": "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale", "attributes": ["code_tfv"], - "around": 2, - "epsg": 2154 + "epsg": 2154, + "notification": false }, { - "id": "Essence intesection", - "layer": "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale", - "attributes": ["code_tfv"], - "epsg": 2154 + "id": "BDTOPO_V3:cours_d_eau", + "layer": "BDTOPO_V3:cours_d_eau", + "attributes": ["toponyme"], + "geom_name": "geometrie", + "around": 2, + "get_geom": true }, { "id": "RPG.LATEST:parcelles_graphiques", @@ -50,6 +45,22 @@ "attributes": ["code_cultu"], "around": 2 }, + { + "id": "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale", + "layer": "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale", + "attributes": ["code_tfv"], + "around": 2, + "epsg": 2154 + }, + { + "id": "BDTOPO_V3:toponymie_lieux_nommes", + "layer": "BDTOPO_V3:toponymie_lieux_nommes", + "attributes": ["graphie_du_toponyme"], + "geom_name": "geometrie", + "around": 1, + "additional_cql": "AND nature_de_l_objet='Bois'", + "notification": false + }, { "id": "BDTOPO_V3:zone_d_activite_ou_d_interet", "layer": "BDTOPO_V3:zone_d_activite_ou_d_interet", @@ -57,7 +68,8 @@ "geom_name": "geometrie", "around": 2, "additional_cql": "AND categorie='Culture et loisirs' AND nature IN ('Abri de montagne', 'Construction', 'Ecomusée', 'Monument', 'Musée', 'Parc de loisirs', 'Parc zoologique', 'Refuge', 'Vestige archéologique')", - "get_geom": true + "get_geom": true, + "notification_uses_next": true }, { "id": "BDTOPO_V3:zone_d_habitation", @@ -66,14 +78,8 @@ "geom_name": "geometrie", "around": 2, "additional_cql": "AND nature IN ('Château')", - "get_geom": true - }, - { - "id": "BDTOPO_V3:cours_d_eau", - "layer": "BDTOPO_V3:cours_d_eau", - "attributes": ["toponyme"], - "geom_name": "geometrie", - "around": 2 + "get_geom": true, + "notification": false }, { "id": "BDTOPO_V3:plan_d_eau", @@ -81,6 +87,7 @@ "attributes": ["nature", "toponyme"], "geom_name": "geometrie", "around": 2, - "additional_cql": "AND nature IN ('Canal', 'Estuaire', 'Retenue', 'Glacier, névé', 'Lac', 'Lagune', 'Mangrove', 'Marais', 'Mare')" + "additional_cql": "AND nature IN ('Canal', 'Estuaire', 'Retenue', 'Glacier, névé', 'Lac', 'Lagune', 'Mangrove', 'Marais', 'Mare')", + "notification": false } ] diff --git a/src/js/event-listeners.js b/src/js/event-listeners.js index 026fa67b..7dc96541 100644 --- a/src/js/event-listeners.js +++ b/src/js/event-listeners.js @@ -122,12 +122,12 @@ function addListeners() { }; // Sauvegarde de l'état de l'application - App.addListener("pause", saveState); + App.addListener("appStateChange", saveState); document.addEventListener("pause", saveState); window.addEventListener("beforeunload", saveState); let keyboardWillHide = false; - if (Capacitor.getPlatform() !== "web") { + if (Capacitor.isNativePlatform()) { Keyboard.addListener("keyboardWillHide", () => { keyboardWillHide = true; }); @@ -238,7 +238,7 @@ function addListeners() { }); App.addListener("resume", () => { - if (Capacitor.getPlatform() === "web") { + if (!Capacitor.isNativePlatform()) { return; } TextZoom.getPreferred().then(value => { diff --git a/src/js/globals.js b/src/js/globals.js index cccc1c76..eec491a0 100644 --- a/src/js/globals.js +++ b/src/js/globals.js @@ -102,6 +102,9 @@ let compareLandmark = null; // Global control offline maps let offlineMaps = null; +// Global control immersive notifications +let immersiveNotifications = null; + // Global flag: is the device connected to the internet? let online = (await Network.getStatus()).connected; @@ -141,6 +144,14 @@ if (!localStorage.getItem("walkingSpeed")) { walkingSpeed = parseFloat(localStorage.getItem("walkingSpeed")) / 3.6; } +// Are new place notifications enabled? +let newPlaceNotifEnabled; +if (!localStorage.getItem("newPlaceNotifEnabled")) { + newPlaceNotifEnabled = 0; +} else { + newPlaceNotifEnabled = parseFloat(localStorage.getItem("newPlaceNotifEnabled")); +} + export default { map, mapRLT1, @@ -181,4 +192,6 @@ export default { landmark, compareLandmark, offlineMaps, + immersiveNotifications, + newPlaceNotifEnabled, }; diff --git a/src/js/immersive-notifications.js b/src/js/immersive-notifications.js new file mode 100644 index 00000000..dfb64664 --- /dev/null +++ b/src/js/immersive-notifications.js @@ -0,0 +1,547 @@ +/** + * Copyright (c) Institut national de l'information géographique et forestière + * + * This program and the accompanying materials are made available under the terms of the GPL License, Version 3.0. + */ + +import { App } from "@capacitor/app"; +import { Capacitor, registerPlugin } from "@capacitor/core"; +const BackgroundGeolocation = registerPlugin("BackgroundGeolocation"); +import { LocalNotifications } from "@capacitor/local-notifications"; +import { Toast } from "@capacitor/toast"; + +import Globals from "./globals"; +import Location from "./services/location"; + +import requestUtils from "./utils/request-utils"; + +import QueryConfig from "./data-layer/immersive-position-config.json"; +import Code_cultuCaption from "./data-layer/code_cultu-caption.json"; +import Code_tfvCaption from "./data-layer/code_tfv-caption.json"; + +import maplibregl from "maplibre-gl"; +import PointToLineDistance from "@turf/point-to-line-distance"; +import CleanCoords from "@turf/clean-coords"; + +let queryConfig; +let code_cultuCaption; +let code_tfvCaption; +try { + const resp = await fetch("https://ignf.github.io/cartes-ign-app/immersive-position-config.json"); + queryConfig = await resp.json(); +} catch (e) { + queryConfig = QueryConfig; +} +try { + const resp = await fetch("https://ignf.github.io/cartes-ign-app/code_cultu-caption.json"); + code_cultuCaption = await resp.json(); +} catch (e) { + code_cultuCaption = Code_cultuCaption; +} +try { + const resp = await fetch("https://ignf.github.io/cartes-ign-app/code_tfv-caption.json"); + code_tfvCaption = await resp.json(); +} catch (e) { + code_tfvCaption = Code_tfvCaption; +} + +/** + * Gestion des "notifications immersives" avec des requêtes faites aux données autour de la géolocalisation + */ +class ImmersiveNotifications { + /** + * constructeur + */ + constructor(test=false) { + this.test = test; + this.lat = null; + this.lng = null; + this.intervalId = null; + this.currentData = {}; + + this.positionWatcherId = null; + this.locationBg = null; + + this.lastNotificationId = 0; + + this.requestNotificationPermission().then( () => { + this.listen(); + }); + } + + /** + * Check and requests if needed the permission to send notifications + */ + async requestNotificationPermission() { + this.permissionStatus = await LocalNotifications.checkPermissions(); + + if (["denied", "prompt", "prompt-with-rationale"].includes(this.permissionStatus.display)) { + this.permissionStatus = await LocalNotifications.requestPermissions(); + if (!["denied", "prompt-with-rationale"].includes(this.permissionStatus.display)) { + console.debug("Notification permission granted"); + } else { + console.warn("Notification permission not granted"); + } + } + } + + /** + * Checks every 60 seconds if the user is inside an area of interest + */ + listen() { + this.intervalId = setInterval(this.#sendNotifications.bind(this), this.test ? 10000 : 120000); + + App.addListener("appStateChange", (state) => { + if (!state.isActive) { + this.#startBgTracking(); + } else { + this.#stopBgTracking(); + } + }); + } + + /** + * Starts background location tracking for notifications + */ + async #startBgTracking() { + if (["denied", "prompt-with-rationale"].includes(this.permissionStatus.display)) { + return; + } + if (this.positionWatcherId !== null) { + return; + } + if (!Capacitor.isNativePlatform()) { + return; + } + // Global parameter to enable/disable notifications + if (!Globals.newPlaceNotifEnabled) { + return; + } + + this.positionWatcherId = await BackgroundGeolocation.addWatcher( + { + backgroundMessage: "Le suivi de position est activé pour envoyer des notification lorque vous arrivez dans un nouveau lieu", + backgroundTitle: "Cartes IGN : notifications d'arrivée dans un lieu", + requestPermissions: true, + distanceFilter: 1000, + }, + async (position, error) => { + if (error) { + console.error("Geolocation error:", error); + return; + } + + this.locationBg = { + lat: position.latitude, + lng: position.longitude, + }; + + this.#sendNotifications(); + + } + ); + } + + /** + * Stops background location tracking for notifications + */ + async #stopBgTracking() { + if (["denied", "prompt-with-rationale"].includes(this.permissionStatus.display)) { + return; + } + if (this.positionWatcherId === null) { + return; + } + if (!Capacitor.isNativePlatform()) { + return; + } + + await BackgroundGeolocation.removeWatcher({ id: this.positionWatcherId }); + this.positionWatcherId = null; + this.locationBg = null; + } + + /** + * Gets the current position and sends notifications according to the position + */ + async #sendNotifications() { + if (["denied", "prompt-with-rationale"].includes(this.permissionStatus.display)) { + return; + } + if (!Location.getCurrentPosition() && !this.test && !this.locationBg) { + return; + } + // Global parameter to enable/disable notifications + if (!Globals.newPlaceNotifEnabled) { + return; + } + + if (this.test) { + this.lat = Globals.map.getCenter().lat; + this.lng = Globals.map.getCenter().lng; + } + + if (this.positionWatcherId === null && Location.getCurrentPosition()) { + this.lat = Location.getCurrentPosition().coords.latitude; + this.lng = Location.getCurrentPosition().coords.longitude; + } else if (this.positionWatcherId !== null && this.locationBg) { + this.lat = this.locationBg.lat; + this.lng = this.locationBg.lng; + } + + this.#computeAll(); + } + + /** + * Computes all data queries + */ + async #computeAll() { + for (let i = 0; i < queryConfig.length; i++) { + const config = queryConfig[i]; + if (config.notification !== false) { + let configNext = null; + if (config.notification_uses_next) { + configNext = queryConfig[i + 1]; + } + const notifWasSent = await this.#computeFromConfig(config, configNext); + if (notifWasSent) { + break; + } + } + } + } + + /** + * Queries GPF's WFS for info defined in the config + */ + async #computeFromConfig(config, configNext = null) { + const result = await requestUtils.requestWfs( + this.lat, + this.lng, + config.layer, + config.attributes, + config.around || 0, + config.geom_name || "geom", + config.additional_cql || "", + config.epsg || 4326, + config.get_geom || false, + ); + + const filteredResult = this.#filterData(config.layer, result); + + if ( + !filteredResult[0] && ( + (config.layer === "BDTOPO_V3:parc_ou_reserve" && this.currentData["BDTOPO_V3:parc_ou_reserve"]) || + (config.layer === "BDTOPO_V3:foret_publique" && this.currentData["BDTOPO_V3:foret_publique"]) + ) + ) { + const notificationText = this.#textTemplateQuit(config.layer); + LocalNotifications.schedule({ + notifications: [ + { + title: "Vous quittez un lieu", + body: notificationText, + id: this.lastNotificationId, + schedule: { + at: new Date(Date.now() + 1000), + allowWhileIdle: true, + }, + }, + ], + }); + this.lastNotificationId++; + if (!Capacitor.isNativePlatform()) { + Toast.show({ + text: notificationText, + duration: "short", + position: "bottom" + }); + console.warn("Notification : " + notificationText); + } + this.currentData[config.layer] = ""; + return true; + } + + if (!filteredResult[0] && config.layer !== "BDTOPO_V3:zone_d_activite_ou_d_interet") { + return false; + } + const layerData = filteredResult[0]; + + if (this.currentData[config.layer] && this.currentData[config.layer] === JSON.stringify(layerData)) { + return false; + } + + let nextLayerData = ""; + + if (configNext !== null) { + const resultNext = await requestUtils.requestWfs( + this.lat, + this.lng, + configNext.layer, + configNext.attributes, + configNext.around || 0, + configNext.geom_name || "geom", + configNext.additional_cql || "", + configNext.epsg || 4326, + configNext.get_geom || false, + ); + if (resultNext[0]) { + const filteredResultNext = this.#filterData(configNext.layer, resultNext); + nextLayerData = filteredResultNext[0]; + } + } + + this.currentData[config.layer] = JSON.stringify(layerData); + + const notificationText = this.#textTemplate(config.layer, layerData, nextLayerData); + LocalNotifications.schedule({ + notifications: [ + { + title: "Nouveau lieu", + body: notificationText, + id: this.lastNotificationId, + schedule: { + at: new Date(Date.now() + 1000), + allowWhileIdle: true, + }, + }, + ], + }); + this.lastNotificationId++; + if (!Capacitor.isNativePlatform()) { + Toast.show({ + text: notificationText, + duration: "short", + position: "bottom" + }); + console.warn("Notification : " + notificationText); + } + return true; + } + + /** + * Notification text template + * @param {String} layer layer name + * @param {String|String[]} result result of the filtered wfs query + * @returns {String} text of the notification + */ + #textTemplate(layer, result, nextResult = "") { + let textResult = ""; + let text; + switch (layer) { + case "LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:commune": + textResult = `🏠 Vous êtes dans la commune de ${result[0]} qui compte ${result[1]} habitants`; + break; + case "LIMITES_ADMINISTRATIVES_EXPRESS.LATEST:departement": + textResult = `🏠 Vous êtes dans le département de ${result}`; + break; + case "BDTOPO_V3:parc_ou_reserve": + if (result[0] === "Site Natura 2000") { + textResult = `🏞️ Vous êtes sur le site ${result[1]} classé Natura 2000 où la faune et la flore sont protégées`; + } else { + textResult = `🏞️ Vous êtes au sein du ${result[1]}`; + } + break; + case "BDTOPO_V3:foret_publique": + textResult = `Vous vous trouvez au sein de ${result}`; + if (nextResult) { + textResult += ` dont l'essence principale est ${nextResult}`; + } + break; + case "BDTOPO_V3:cours_d_eau": + textResult = `🌊 Non loin se trouve le cours d'eau ${result[0]}`; + break; + case "RPG.LATEST:parcelles_graphiques": + textResult = `L'agriculture alentours est consacrée à des cultures telles que ${result}`; + break; + case "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale": + textResult = `L'essence principale des bois environnants est ${result}`; + break; + case "BDTOPO_V3:zone_d_activite_ou_d_interet": + text = ""; + if (result && !nextResult) { + text = result[0]; + } else if (!result && nextResult) { + text = nextResult[0]; + } else if (result && nextResult) { + if (result[1] < nextResult[1]) { + text = result[0]; + } else { + text = nextResult[0]; + } + } + textResult = `Vous êtes à proximité de ${text}`; + break; + + default: + text = result[0]; + if (Array.isArray(text)) { + text = text[0]; + } + textResult = `Vous arrivez dans ${text}`; + break; + } + return textResult; + } + + /** + * Notification text template for quitting an area + * @param {String} layer + * @returns {String} text of the notification + */ + #textTemplateQuit(layer) { + let notifText = ""; + const data = JSON.parse(this.currentData[layer]); + switch (layer) { + case "BDTOPO_V3:parc_ou_reserve": + if (data[0] === "Site Natura 2000") { + notifText = "Vous n'êtes plus sur un site classé Natura 2000"; + } else { + notifText = `Vous quittez le ${data[1]}`; + } + break; + case "BDTOPO_V3:foret_publique": + notifText = `Vous avez quitté ${data}`; + break; + default: + notifText = "Vous quittez un lieu"; + break; + } + return notifText; + } + + /** + * Filters the data results according to specific rules + * @param {String} layer + * @param {Array} dataResults + * @returns + */ + #filterData(layer, dataResults) { + if (layer === "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale") { + dataResults = dataResults.filter( (essence) => essence !== "NC" && essence !== "NR"); + } + if (layer === "BDTOPO_V3:parc_ou_reserve") { + dataResults = dataResults.filter( (parc) => { + if ( !(["Site Natura 2000", "Parc naturel régional", "Parc national", "Réserve naturelle"].includes(parc[0])) ) { + return false; + } + if (["Périmètre de protection d'une réserve naturelle nationale", "Périmètre de protection d'une réserve naturelle régionale"].includes(parc[2])) { + return false; + } + return true; + }).sort((a, b) => { + if (a[0] === "Parc national") { + return -1; + } + if (b[0] === "Parc national") { + return 1; + } + if (b[0] === "Parc naturel régional" && a[0] !== "Parc national") { + return 1; + } + if (a[0] === "Parc naturel régional" && b[0] !== "Parc national") { + return -1; + } + if (a[0] === "Site Natura 2000") { + return 1; + } + if (b[0] === "Site Natura 2000") { + return -1; + } + }); + } + if (layer === "BDTOPO_V3:zone_d_activite_ou_d_interet") { + dataResults = dataResults.filter( (zai) => zai[1] !== null).sort( (a, b) => { + const coordsA = new maplibregl.LngLat(...a[2].coordinates[0][0][0]); + const coordsB = new maplibregl.LngLat(...b[2].coordinates[0][0][0]); + const coordsRef = new maplibregl.LngLat(this.lng, this.lat); + return coordsRef.distanceTo(coordsA) - coordsRef.distanceTo(coordsB); + }).map( feat => { + const coordsRef = new maplibregl.LngLat(this.lng, this.lat); + const coordsFeat = new maplibregl.LngLat(...feat[2].coordinates[0][0][0]); + return [feat[1], coordsRef.distanceTo(coordsFeat)]; + }).slice(0, 1); + } + if (layer === "BDTOPO_V3:plan_d_eau") { + dataResults = dataResults.filter( (plan) => plan[1] !== null); + } + if (layer === "RPG.LATEST:parcelles_graphiques") { + dataResults = dataResults.map( (code_cultu) => code_cultuCaption[code_cultu]).filter((culture) => culture); + const counts = {}; + dataResults.forEach( (culture) => { + if (counts[culture]) { + counts[culture]++; + } else { + counts[culture] = 1; + } + }); + dataResults.sort( (a, b) => counts[a] > counts[b] ); + } + if (layer === "LANDCOVER.FORESTINVENTORY.V2:formation_vegetale") { + dataResults = dataResults.map( (code_tfv) => { + let essenceEmoji = ""; + if (code_tfvCaption[code_tfv]) { + if (code_tfv[2] === "1") { + essenceEmoji = " 🌳"; + } + if (code_tfv[2] === "2") { + essenceEmoji = " 🌲"; + } + } + return code_tfvCaption[code_tfv] + essenceEmoji; + }).filter((essence) => essence); + const counts = {}; + dataResults.forEach( (essence) => { + if (counts[essence]) { + counts[essence]++; + } else { + counts[essence] = 1; + } + }); + dataResults.sort( (a, b) => counts[a] > counts[b] ); + } + if (layer === "BDTOPO_V3:zone_d_habitation") { + dataResults = dataResults.filter( (feat) => feat[0]).sort( (a, b) => { + const coordsA = new maplibregl.LngLat(...a[1].coordinates[0][0][0]); + const coordsB = new maplibregl.LngLat(...b[1].coordinates[0][0][0]); + const coordsRef = new maplibregl.LngLat(this.lng, this.lat); + return coordsRef.distanceTo(coordsA) - coordsRef.distanceTo(coordsB); + }).map( feat => { + const coordsRef = new maplibregl.LngLat(this.lng, this.lat); + const coordsFeat = new maplibregl.LngLat(...feat[1].coordinates[0][0][0]); + return [feat[0], coordsRef.distanceTo(coordsFeat)]; + }).slice(0, 1); + } + if (layer === "BDTOPO_V3:cours_d_eau") { + dataResults = dataResults.filter( (cours) => { + if (cours[0].split(" ")[0] === "Bras") { + return false; + } + const splitted = cours[0].split(" "); + for (let word of splitted) { + if (word.match(/[0-9][0-9]/g)) { + return false; + } + } + return true; + }).sort( (a, b) => { + if (a[1].type === "MultiLineString") { + a[1].type = "LineString"; + a[1].coordinates = a[1].coordinates[0]; + } + if (b[1].type === "MultiLineString") { + b[1].type = "LineString"; + b[1].coordinates = b[1].coordinates[0]; + } + const distanceA = PointToLineDistance([this.lng, this.lat], CleanCoords(a[1])); + const distanceB = PointToLineDistance([this.lng, this.lat], CleanCoords(b[1])); + return distanceA - distanceB; + }); + } + return dataResults; + } + + +} + +export default ImmersiveNotifications; diff --git a/src/js/immersive-position.js b/src/js/immersive-position.js index 2913eb96..7cb43788 100644 --- a/src/js/immersive-position.js +++ b/src/js/immersive-position.js @@ -4,20 +4,21 @@ * This program and the accompanying materials are made available under the terms of the GPL License, Version 3.0. */ -import QueryConfig from "./data-layer/immersvie-position-config.json"; +import QueryConfig from "./data-layer/immersive-position-config.json"; import Code_cultuCaption from "./data-layer/code_cultu-caption.json"; import Code_tfvCaption from "./data-layer/code_tfv-caption.json"; import maplibregl from "maplibre-gl"; -import proj4 from "proj4"; +import PointToLineDistance from "@turf/point-to-line-distance"; +import CleanCoords from "@turf/clean-coords"; -proj4.defs("EPSG:2154","+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"); +import requestUtils from "./utils/request-utils"; let queryConfig; let code_cultuCaption; let code_tfvCaption; try { - const resp = await fetch("https://ignf.github.io/cartes-ign-app/immersvie-position-config.json"); + const resp = await fetch("https://ignf.github.io/cartes-ign-app/immersive-position-config.json"); queryConfig = await resp.json(); } catch (e) { queryConfig = QueryConfig; @@ -320,16 +321,20 @@ class ImmersivePosion extends EventTarget { } if (layer === "BDTOPO_V3:cours_d_eau") { dataResults = dataResults.filter( (cours) => { - if (cours.split(" ")[0] === "Bras") { + if (cours[0].split(" ")[0] === "Bras") { return false; } - const splitted = cours.split(" "); + const splitted = cours[0].split(" "); for (let word of splitted) { if (word.match(/[0-9][0-9]/g)) { return false; } } return true; + }).sort( (a, b) => { + const distanceA = PointToLineDistance([this.lng, this.lat], CleanCoords(a[1])); + const distanceB = PointToLineDistance([this.lng, this.lat], CleanCoords(b[1])); + return distanceA - distanceB; }); } return dataResults; @@ -346,40 +351,12 @@ class ImmersivePosion extends EventTarget { * @returns {Promise(Array)} results of each attributes (no duplicates) */ async #computeGenericGPFWFS(layer, attributes, around=0, geom_name="geom", additional_cql="", epsg=4326, getGeom=false) { - let coord1 = this.lat; - let coord2 = this.lng; - if (epsg !== 4326) { - [coord1, coord2] = proj4(proj4.defs("EPSG:4326"), proj4.defs(`EPSG:${epsg}`), [this.lng, this.lat]); - } - let cql_filter = `INTERSECTS(${geom_name},Point(${coord1}%20${coord2}))`; - if (around > 0) { - cql_filter = `DWITHIN(${geom_name},Point(${coord1}%20${coord2}),${around},kilometers)`; - } - if (additional_cql) { - cql_filter += ` ${additional_cql}`; - } - - const results = await fetch( - `https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=${layer}&outputFormat=json&count=50&CQL_FILTER=${cql_filter}` + const results_attributes = await requestUtils.requestWfs( + this.lat, this.lng, layer, attributes, around, geom_name, additional_cql, epsg, getGeom + ); + return Array.from( + new Set( this.#filterData(layer, results_attributes) ) ); - const json = await results.json(); - - const results_attributes = []; - json.features.forEach((feature) => { - const feature_attributes = []; - attributes.forEach((attribute) => { - feature_attributes.push(feature.properties[attribute]); - }); - if (getGeom) { - feature_attributes.push(feature.geometry); - } - if (attributes.length === 1 && feature_attributes[0] !== null && !getGeom) { - results_attributes.push(feature_attributes[0]); - } else if (attributes.length > 1 || getGeom) { - results_attributes.push(feature_attributes); - } - }); - return Array.from( new Set(this.#filterData(layer, results_attributes)) ); } } diff --git a/src/js/index.js b/src/js/index.js index d73d2a80..462ff6a6 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -170,7 +170,7 @@ function app() { }); }); }, 500); - if (Capacitor.getPlatform() !== "web") { + if (Capacitor.isNativePlatform()) { TextZoom.getPreferred().then(value => { const newValue = Math.min(1.5, value.value); TextZoom.set({ diff --git a/src/js/my-account/my-account.js b/src/js/my-account/my-account.js index 6cf6a12a..37557ec9 100644 --- a/src/js/my-account/my-account.js +++ b/src/js/my-account/my-account.js @@ -1204,7 +1204,7 @@ ${props.text}`, encoding: Encoding.UTF8, }); // For testing purposes - if (Capacitor.getPlatform() === "web") { + if (!Capacitor.isNativePlatform()) { jsUtils.download(`${route.name.replace(/[&/\\#,+()$~%.'":*?<>{}]/g, "_")}.geojson`, JSON.stringify(this.#routeToGeojson(route))); } } else if (value === "gpx") { @@ -1278,7 +1278,7 @@ ${props.text}`, documentsName = "Fichiers"; } // For testing purposes - if (Capacitor.getPlatform() === "web") { + if (!Capacitor.isNativePlatform()) { jsUtils.download(`${landmark.properties.title.replace(/[&/\\#,+()$~%.'":*?<>{}]/g, "_")}.geojson`, JSON.stringify({ type: "Feature", geometry: landmark.geometry, diff --git a/src/js/services/location.js b/src/js/services/location.js index 066a4cc1..3e106ade 100644 --- a/src/js/services/location.js +++ b/src/js/services/location.js @@ -382,7 +382,7 @@ const enablePosition = async() => { return; } } - if (["denied", "prompt", "prompt-with-rationale"].includes(permissionStatus.location) && Capacitor.getPlatform() !== "web") { + if (["denied", "prompt", "prompt-with-rationale"].includes(permissionStatus.location) && Capacitor.isNativePlatform()) { permissionStatus = await Geolocation.requestPermissions(["location"]); } if (["denied", "prompt-with-rationale"].includes(permissionStatus.location)) { diff --git a/src/js/utils/request-utils.js b/src/js/utils/request-utils.js new file mode 100644 index 00000000..74c5bb0a --- /dev/null +++ b/src/js/utils/request-utils.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) Institut national de l'information géographique et forestière + * + * This program and the accompanying materials are made available under the terms of the GPL License, Version 3.0. + */ + +import proj4 from "proj4"; +proj4.defs("EPSG:2154","+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"); + +/** + * Requests WFS data in a given location for a given layer of Geoplateforme's WFS + * @param {number} lat latitude of the point + * @param {number} lng longitude of the point + * @param {string} layer name of the WFS layer + * @param {Array} attributes list of strings of the relevant attributes to return + * @param {number} around distance around the point in km for the query, default 0 + * @param {string} geom_name name of the geometry column, default "geom" + * @param {string} additional_cql cql filter needed other than geometry, e.g. "AND nature_de_l_objet='Bois'", default "" + * @param {number} epsg epsg number of the layer's CRS, default 4326 + * @returns {Promise(Array)} results of each attributes (no duplicates) + */ +async function requestWfs(lat, lng, layer, attributes, around=0, geom_name="geom", additional_cql="", epsg=4326, getGeom=false) { + let coord1 = lat; + let coord2 = lng; + if (epsg !== 4326) { + [coord1, coord2] = proj4(proj4.defs("EPSG:4326"), proj4.defs(`EPSG:${epsg}`), [lng, lat]); + } + let cql_filter = `INTERSECTS(${geom_name},Point(${coord1}%20${coord2}))`; + if (around > 0) { + cql_filter = `DWITHIN(${geom_name},Point(${coord1}%20${coord2}),${around},kilometers)`; + } + if (additional_cql) { + cql_filter += ` ${additional_cql}`; + } + + const results = await fetch( + `https://data.geopf.fr/wfs/ows?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=${layer}&outputFormat=json&count=50&CQL_FILTER=${cql_filter}` + ); + const json = await results.json(); + + const results_attributes = []; + json.features.forEach((feature) => { + const feature_attributes = []; + let allNull = true; + attributes.forEach((attribute) => { + feature_attributes.push(feature.properties[attribute]); + if (feature.properties[attribute] !== null) { + allNull = false; + } + }); + if (getGeom) { + feature_attributes.push(feature.geometry); + } + if (attributes.length === 1 && feature_attributes[0] !== null && !getGeom) { + results_attributes.push(feature_attributes[0]); + } else if (!allNull && (attributes.length > 1 || getGeom)) { + results_attributes.push(feature_attributes); + } + }); + return results_attributes; +} + + +export default { + requestWfs, +};