From 8046feadb0e1a04f8a106c93e51324382ba4883f Mon Sep 17 00:00:00 2001 From: Sebastien Riviere Date: Mon, 2 Mar 2026 01:33:20 +0100 Subject: [PATCH] Server heavy refactoring 2 (not functionnal) --- server/traque-back/package-lock.json | 3947 +++++++++++++++++ server/traque-back/package.json | 1 + server/traque-back/src/config/events.js | 36 + server/traque-back/src/config/game.js | 44 + server/traque-back/src/config/server.js | 6 + server/traque-back/src/config/zone.js | 13 + .../src/core/factories/game_zone_factory.js | 70 + .../core/factories/placement_zone_factory.js | 9 + server/traque-back/src/core/game_manager.js | 140 - .../src/core/managers/game_manager.js | 122 + .../src/core/managers/team_manager.js | 59 + .../src/core/managers/zone_manager.js | 73 + server/traque-back/src/core/models/team.js | 32 + server/traque-back/src/core/models/zone.js | 28 + .../src/core/states/game/default_state.js | 44 + .../src/core/states/game/finished_state.js | 37 + .../traque-back/src/core/states/game/index.js | 4 + .../src/core/states/game/placement_state.js | 42 + .../src/core/states/game/playing_state.js | 93 + .../src/core/states/teams/default_team.js | 14 + .../src/core/states/teams/finished_team.js | 14 + .../src/core/states/teams/placement_team.js | 25 + .../src/core/states/teams/playing_team.js | 100 + server/traque-back/src/core/zone_manager.js | 242 - server/traque-back/src/externals/api/photo.js | 65 + .../src/externals/handlers/adminHandler.js | 79 + .../src/externals/handlers/playerHandler.js | 80 + .../src/externals/mappers/admin_mapper.js | 69 + .../src/externals/mappers/player_mapper.js | 54 + .../synchronizers/admin_synchronizer.js | 24 + .../synchronizers/player_synchronizer.js | 39 + server/traque-back/src/index.js | 36 +- server/traque-back/src/old/admin_socket.js | 98 - server/traque-back/src/old/game.js | 504 --- server/traque-back/src/old/team_socket.js | 146 - server/traque-back/src/old/timeout_handler.js | 92 - server/traque-back/src/old/trajectory.js | 90 - server/traque-back/src/services/photo.js | 86 - server/traque-back/src/socket/adminHandler.js | 124 - .../traque-back/src/socket/playerHandler.js | 111 - .../traque-back/src/states/default_state.js | 19 - .../traque-back/src/states/finished_state.js | 19 - server/traque-back/src/states/game_state.js | 36 - .../traque-back/src/states/placement_state.js | 36 - .../traque-back/src/states/playing_state.js | 174 - .../src/team/mapper/default_team_mapper.js | 20 - .../src/team/mapper/finished_team_mapper.js | 20 - .../src/team/mapper/placement_team_mapper.js | 26 - .../src/team/mapper/playing_team_mapper.js | 56 - .../src/team/mapper/team_mapper.js | 23 - server/traque-back/src/team/team.js | 19 - server/traque-back/src/util/circular_map.js | 33 - server/traque-back/src/util/random.js | 1 + server/traque-back/src/util/scheduler.js | 56 + server/traque-back/src/util/state_tracker.js | 14 + .../traque-back/src/util/timeout_manager.js | 26 - server/traque-back/src/util/util.js | 22 - 57 files changed, 5320 insertions(+), 2172 deletions(-) create mode 100644 server/traque-back/package-lock.json create mode 100644 server/traque-back/src/config/events.js create mode 100644 server/traque-back/src/config/game.js create mode 100644 server/traque-back/src/config/server.js create mode 100644 server/traque-back/src/config/zone.js create mode 100644 server/traque-back/src/core/factories/game_zone_factory.js create mode 100644 server/traque-back/src/core/factories/placement_zone_factory.js delete mode 100644 server/traque-back/src/core/game_manager.js create mode 100644 server/traque-back/src/core/managers/game_manager.js create mode 100644 server/traque-back/src/core/managers/team_manager.js create mode 100644 server/traque-back/src/core/managers/zone_manager.js create mode 100644 server/traque-back/src/core/models/team.js create mode 100644 server/traque-back/src/core/models/zone.js create mode 100644 server/traque-back/src/core/states/game/default_state.js create mode 100644 server/traque-back/src/core/states/game/finished_state.js create mode 100644 server/traque-back/src/core/states/game/index.js create mode 100644 server/traque-back/src/core/states/game/placement_state.js create mode 100644 server/traque-back/src/core/states/game/playing_state.js create mode 100644 server/traque-back/src/core/states/teams/default_team.js create mode 100644 server/traque-back/src/core/states/teams/finished_team.js create mode 100644 server/traque-back/src/core/states/teams/placement_team.js create mode 100644 server/traque-back/src/core/states/teams/playing_team.js delete mode 100644 server/traque-back/src/core/zone_manager.js create mode 100644 server/traque-back/src/externals/api/photo.js create mode 100644 server/traque-back/src/externals/handlers/adminHandler.js create mode 100644 server/traque-back/src/externals/handlers/playerHandler.js create mode 100644 server/traque-back/src/externals/mappers/admin_mapper.js create mode 100644 server/traque-back/src/externals/mappers/player_mapper.js create mode 100644 server/traque-back/src/externals/synchronizers/admin_synchronizer.js create mode 100644 server/traque-back/src/externals/synchronizers/player_synchronizer.js delete mode 100644 server/traque-back/src/old/admin_socket.js delete mode 100644 server/traque-back/src/old/game.js delete mode 100644 server/traque-back/src/old/team_socket.js delete mode 100644 server/traque-back/src/old/timeout_handler.js delete mode 100644 server/traque-back/src/old/trajectory.js delete mode 100644 server/traque-back/src/services/photo.js delete mode 100644 server/traque-back/src/socket/adminHandler.js delete mode 100644 server/traque-back/src/socket/playerHandler.js delete mode 100644 server/traque-back/src/states/default_state.js delete mode 100644 server/traque-back/src/states/finished_state.js delete mode 100644 server/traque-back/src/states/game_state.js delete mode 100644 server/traque-back/src/states/placement_state.js delete mode 100644 server/traque-back/src/states/playing_state.js delete mode 100644 server/traque-back/src/team/mapper/default_team_mapper.js delete mode 100644 server/traque-back/src/team/mapper/finished_team_mapper.js delete mode 100644 server/traque-back/src/team/mapper/placement_team_mapper.js delete mode 100644 server/traque-back/src/team/mapper/playing_team_mapper.js delete mode 100644 server/traque-back/src/team/mapper/team_mapper.js delete mode 100644 server/traque-back/src/team/team.js delete mode 100644 server/traque-back/src/util/circular_map.js create mode 100644 server/traque-back/src/util/random.js create mode 100644 server/traque-back/src/util/scheduler.js create mode 100644 server/traque-back/src/util/state_tracker.js delete mode 100644 server/traque-back/src/util/timeout_manager.js delete mode 100644 server/traque-back/src/util/util.js diff --git a/server/traque-back/package-lock.json b/server/traque-back/package-lock.json new file mode 100644 index 0000000..8c44503 --- /dev/null +++ b/server/traque-back/package-lock.json @@ -0,0 +1,3947 @@ +{ + "name": "traque-back", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "traque-back", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@turf/turf": "^7.3.4", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "multer": "^1.4.5-lts.1", + "random-location": "^1.1.3", + "socket.io": "^4.7.5" + }, + "devDependencies": { + "nodemon": "^3.1.10" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@turf/along": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-7.3.4.tgz", + "integrity": "sha512-PvIoXin0I1t3nRwJz7uqR6fsxDMqdGwJq90qGOeqkNwlZqlF+5o2wKHPwYwi0RXZhLvxRP5qlbNIvV8ADdbWxw==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.4", + "@turf/destination": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/angle": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-7.3.4.tgz", + "integrity": "sha512-235JAfbrNMjHQXQfd/p+fYnlfCHsQsKHda5Eeyc+/jIY0s5mKvhcxgFaOEnigA2q1n+PrVOExs3BViGTKnWhAg==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/area": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.4.tgz", + "integrity": "sha512-UEQQFw2XwHpozSBAMEtZI3jDsAad4NnHL/poF7/S6zeDCjEBCkt3MYd6DSGH/cvgcOozxH/ky3/rIVSMZdx4vA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.3.4.tgz", + "integrity": "sha512-D5ErVWtfQbEPh11yzI69uxqrcJmbPU/9Y59f1uTapgwAwQHQztDWgsYpnL3ns8r1GmPWLP8sGJLVTIk2TZSiYA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-7.3.4.tgz", + "integrity": "sha512-HCn0q/WPVEE9Dztg7tCvClOPrrh9MoxNUk73byHvcZLBcvziN6F84f/ZbFcbQSh8hgOeVMs/keeqWMqsICcNLg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-polygon": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-7.3.4.tgz", + "integrity": "sha512-XCDYQwCA41Bum3R1xX0Na1nR4ozoe/pCYy5bxqrzyMs87kPJUIfBrD5IWxjnZyLqFpfEpolMHJz5ed1uA2PanQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-7.3.4.tgz", + "integrity": "sha512-zvFjapyFaOrM8nBtAND7f4yb0BJV0jyj6cyoXyTYqLY+3Hn0eHgL0M8lwxDLbTom5KfqYDHDVDQC3+VSfypoEA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bezier-spline": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-7.3.4.tgz", + "integrity": "sha512-+iDUeiBKByIs/6K5WW8pG6IDxrRLJHFLM80zSpzk2xBtgy3mq36NZwwt67Pu7EJAkc9GUXKIm9SkspoKue9aYQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-clockwise": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-7.3.4.tgz", + "integrity": "sha512-X/O+u/OsoJ99mujhlqviuB7HX0tdJ5931TBjNSseps43XtROVuB5PwBDgwKfu5lY1B4DSGAxbbxJ795RmPnguQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-concave": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-concave/-/boolean-concave-7.3.4.tgz", + "integrity": "sha512-SHuAzjqaAes6ELDZcN/FKZWCQZsqwYv3gMosoLRFWTwKyBQe8i29e4y6XnXakDr1uklVUeRRcdhZ5oKtX9ABPQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-contains": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-7.3.4.tgz", + "integrity": "sha512-AJMGbtC6HiXgHvq0RNlTfsDB58Qf9Js45MP/APbhGTH4AiLZ8VMDISywVFNd7qN6oppNlDd3xApVR28+ti8bNg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/boolean-point-on-line": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-split": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-crosses": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-7.3.4.tgz", + "integrity": "sha512-v/U3SuGdkexfLTMhho6Vj0OjqPUeYdThxp8zggGJ1VHow27fvLLez0DjUR3AftHjjHM6bRzZoNsu2qUlEe5hjw==", + "license": "MIT", + "dependencies": { + "@turf/boolean-equal": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@turf/polygon-to-line": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-disjoint": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-7.3.4.tgz", + "integrity": "sha512-Dl4O27ygi2NqskGQuvSlDLJYlJ2SPkHb3A9T/v6eAudjlMiKdEY6bMxKUfU5y+Px1WiCZxd+9rXGXJgGC3WiQg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/polygon-to-line": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-equal": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-7.3.4.tgz", + "integrity": "sha512-AhWqe7D1o0wp3d3QQRSqgWDI8s1JfTFKFe9rU5mrSxYPGlmaQsJC07RCaYfFiGym9lACd1lxBJiPidCbLaPOfw==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "geojson-equality-ts": "^1.0.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-7.3.4.tgz", + "integrity": "sha512-sxi41NXkb5hrJgOvpm32hyBLhW8fem0vn2XxR4+jyRg1rM/v3ziF10/VqC9KDZuDNZkt9JjL9B0825Cf7AN6Lg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-disjoint": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-overlap": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-7.3.4.tgz", + "integrity": "sha512-Q3dlswIuqffSiMfln7xa36YDnN1TWtERMF/155rzjglm4NTUG/6S+gNsb8s6qpLjc+hN6btCq1ZjxAWurPf8Vg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@turf/line-overlap": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "geojson-equality-ts": "^1.0.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-parallel": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-7.3.4.tgz", + "integrity": "sha512-sTNMqsUkLPnSJEqc2IZ5ig3nHRoubyOH2HW1LILqOybCJI630FEM9UoYP1pZniF5nwTyCjQWnXA1FxusVILuFQ==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/line-segment": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.3.4.tgz", + "integrity": "sha512-v/4hfyY90Vz9cDgs2GwjQf+Lft8o7mNCLJOTz/iv8SHAIgMMX0czEoIaNVOJr7tBqPqwin1CGwsncrkf5C9n8Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "point-in-polygon-hao": "^1.1.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-7.3.4.tgz", + "integrity": "sha512-70gm5x6YQOZKcw0b/O4jjMwVWnFj+Zb6TXozLgZFDZShc8pgTQtZku7K+HKZ7Eya+7usHIB4IimZauomOMa+iw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-touches": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-touches/-/boolean-touches-7.3.4.tgz", + "integrity": "sha512-XOwhjc0oCWhnBUB+l4drpXcg7mkNXPX3SuSz/Xv7gvLH/yRrBwzVGllzK1AHlGU9BVkGVBJIZGYX7jgTM681NQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/boolean-point-on-line": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-valid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-valid/-/boolean-valid-7.3.4.tgz", + "integrity": "sha512-P6M9BtRvzFF2N5g+1/DTIbYGpEbwQ2sv/Pw+uj11P3NYAA9VE8mvrxFYf+CowFdSfY6bY4ejhuqKhrTmAMv7wA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/boolean-crosses": "7.3.4", + "@turf/boolean-disjoint": "7.3.4", + "@turf/boolean-overlap": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/boolean-point-on-line": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@types/geojson": "^7946.0.10", + "geojson-polygon-self-intersections": "^1.2.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-within": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-7.3.4.tgz", + "integrity": "sha512-eLgi803gz0KcYkyxnnqnz9Vd6tw2/0eAExe/Rq8sO0dqypaSiomSumxjqu89d/yo24Qz8gW7c0kJ6YihNbMYxA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/boolean-point-on-line": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-split": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.3.4.tgz", + "integrity": "sha512-MVOCBDuOl3KGDsh2stW12RmiFaFeSkVjeUbZ+ADUtIVnv+jlFsmjBpFtsEw8s9YQn5g0667QppOshm0FBHA57Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/center": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/jsts": "^2.7.1", + "@turf/meta": "7.3.4", + "@turf/projection": "7.3.4", + "@types/geojson": "^7946.0.10", + "d3-geo": "1.7.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.3.4.tgz", + "integrity": "sha512-4SsLMDHWthXbyIHsczgFCo4fx+8tC8w2+B5HdEuY+P+cSOOL4T+6QQzd7WWjuN/Y3ndowFssUmwRrvXuwVRxQA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-7.3.4.tgz", + "integrity": "sha512-6foVk5HLjlSPr48EI686Eis6/bYrJiHjKQlwY/7YlJc1uDitsIjPw2LjUCGIUZDEd6PdNUgg1+LgI7klXYvW3A==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-median": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-7.3.4.tgz", + "integrity": "sha512-Bz6rDr0plQOGSXgT3X3t941pYd44a5vIY8OEt4Y11H1BsgpmzFc6g7L5mr7FXW/uiYGxOewAfNcVUYUdJf9kMg==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-of-mass": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-7.3.4.tgz", + "integrity": "sha512-mOSupDF5qxQTA/kOWYletHcBJQ3S2gVl/IRgrBH/YY9yiFq6UGRpZ0sNcIML4H06u/1DY/jqqG+d1nc/1yIA6Q==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.4", + "@turf/convex": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.3.4.tgz", + "integrity": "sha512-6c3kyTSKBrmiPMe75UkHw6MgedroZ6eR5usEvdlDhXgA3MudFPXIZkMFmMd1h9XeJ9xFfkmq+HPCdF0cOzvztA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-7.3.4.tgz", + "integrity": "sha512-6ccr5iT51/XONF+pbpkqoRxKX4ZVWLubXb1frGCnClv2suo1UIY9SIlINNctVDupXd2P9PpqZCbrXATrcrokPg==", + "license": "MIT", + "dependencies": { + "@turf/destination": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clean-coords": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-7.3.4.tgz", + "integrity": "sha512-S61aJXLvPN/uZHtjzmJbLv7xhi28Sq3PshCIZSvno4Mo45bvl79Vg4aZskrG05AaSSbipplqfH+MZrkW9Xboeg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-on-line": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-7.3.4.tgz", + "integrity": "sha512-pwQ+RyQw986uu7IulY/18NRAebwZZScb084bvVqVkTrllwLSv4oVBqUxmUMiwtp+PNdiRGRFOvNyZqtRsiD+Jw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-7.3.4.tgz", + "integrity": "sha512-+zoSyiF0LilXy4Tr0/lC7IgqbTMZZ2wwP3iSrqre58b61pUtdhCnBcjA2r8FkcW7z3GMbGf5XkIWhO+b+vDSsw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-7.3.4.tgz", + "integrity": "sha512-RkuXf767Shk0AfY+fh0PASVw8YR4H8zYR7XQrCgWd/bCuh6CXs7rWZ6UTLu/PiA6y6WsIhyAQv4LhNH5kCzpbA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "@types/geokdbush": "^1.1.5", + "geokdbush": "^2.0.1", + "kdbush": "^4.0.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-kmeans": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-7.3.4.tgz", + "integrity": "sha512-89mlwhcb+vyZAKX0eBa3LQ8VyIKLayrzJpKGb90sEkIu0hDua9JCE+zlbaPoUAvAqflEiX+poFFuh7pngtsBMg==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "skmeans": "0.9.7", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/collect": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-7.3.4.tgz", + "integrity": "sha512-fG28oDZK4HCXC/AhF0pmHKLtI9DWwdJr/ktuWolrqzA5b1G7eawrXwDu8B5I3sXhdWonNRMcuLbIuz+XQscHKw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-7.3.4.tgz", + "integrity": "sha512-wNp9ar4FfpTfQXLZWXQ/jUBBoUFOwRN/mmlv5xrhoYFpP/F5SNy7GVDMZXaBfHdUUplfJUPF5hIKQlCUR8+k3A==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/concave": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-7.3.4.tgz", + "integrity": "sha512-HZa1CV2pv4Xpcoe3t5S3ZW6j9jVbc27exzKwZWF7MlFxSz4BKRirWiME8Fku8nvQcGafpfLc+Lwpma+nGvg06w==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/tin": "7.3.4", + "@types/geojson": "^7946.0.10", + "topojson-client": "3.x", + "topojson-server": "3.x", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/convex": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-7.3.4.tgz", + "integrity": "sha512-zeNv0fFdOoHuOQB7nl6OLb0DyjvzDvm0e3zlFkph50GF9pEKOmkCSmlniw681aWL2aRBdWZBnON3rRzOS+9C7Q==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "concaveman": "^1.2.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-7.3.4.tgz", + "integrity": "sha512-YxoUJwkKmTHiRFQxMQOP0tz8Vy+ga5EXl+C+F/WubjDLwT1AJu5y8CNIjLvWyjPWckj/vZG4u/1js5bx6MLADA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/difference": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-7.3.4.tgz", + "integrity": "sha512-kIxizNQrYLO2rtqUIeed0tPycicrXoipy/g9d4mjv91kzBEbwpyojz9zi8U9G1ISBfCEgA7wsViQD0r+8qzxXw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/dissolve": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-7.3.4.tgz", + "integrity": "sha512-xjGY1gQ4icWhDgsW0YfU2KQtij1+ru34AfvtkVMQEgI86O9EwjW2r9Jq5DJY2PMKPbor3kz9yM/RTOiDP7f3Jg==", + "license": "MIT", + "dependencies": { + "@turf/flatten": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-7.3.4.tgz", + "integrity": "sha512-9drWgd46uHPPyzgrcRQLgSvdS/SjVlQ6ZIBoRQagS5P2kSjUbcOXHIMeOSPwfxwlKhEtobLyr+IiR2ns1TfF8w==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-7.3.4.tgz", + "integrity": "sha512-dVMNEmIluKgn7iQTmzJJOe0UASRNmmSdFX1boAev5MISaW3AvPiURCCOV+lTIeoaQbWRpEAESbAp6JIimXFr8Q==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/ellipse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-7.3.4.tgz", + "integrity": "sha512-SMgbERZl12j7H8YaIofmnf0NwAvdF5Wly4tjI/eUhj/sFOKrKXOS1lvCSBJ6uSV9tFijl3ecGOVOlTpURdZ30g==", + "license": "MIT", + "dependencies": { + "@turf/destination": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/transform-rotate": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/envelope": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-7.3.4.tgz", + "integrity": "sha512-anXSjYMXGAyXT7rpO74VyRI0q/rPAbKE/MYvou+QvG0U/Oa7el0yF4JNNi9wKEAxXg/10aWm9kHp8s2caeLg6A==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/bbox-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/explode": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-7.3.4.tgz", + "integrity": "sha512-7QWhp3f8jhrWjvArhJ74hXBFHMaiJr/2Y1PzHCWue2/pC5MbbTV0o7peehwrrrJC/1uD6CVb3hlcb77IxtMQkw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-7.3.4.tgz", + "integrity": "sha512-Yt3HCh/qeNaXS4LYhXczFhBfTeaKlTBoxEw1OICb9RT3SiGU0XCxuK7H0W26OLo7XxB0qP7GPs2L3FZbiri6wQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flip": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-7.3.4.tgz", + "integrity": "sha512-HME+kVMTyvcsYVY6dC6DTvuzq8vvDpw+C7PviEqpuT3KcVlBCoGPAqlWRdyWYOb9MDciOqNxvvJF/okpb/GQcg==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/geojson-rbush": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/geojson-rbush/-/geojson-rbush-7.3.4.tgz", + "integrity": "sha512-aDG/5mMCgKduqBwZ3XpLOdlE2hizV3fM+5dHCWyrBepCQLeM/QRvvpBDCdQKDWKpoIBmrGGYDNiOofnf3QmGhg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/great-circle": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-7.3.4.tgz", + "integrity": "sha512-JvfzWFL9efP+xKtOnKzGvwEIXfaN0CLZoPPxNnWa/cVisLs9FVMlC9PWnuL3/3aqH5VhBHPddmU8ipzNE6KIIA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "arc": "^0.2.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.3.4.tgz", + "integrity": "sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/hex-grid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-7.3.4.tgz", + "integrity": "sha512-TDCgBykFdsrP3IOOfToiiLpYkbUb3eEEhM9riIqWht0ubKUY61LN7qVs9bxZD83hG6XaDB6uY7SWkxK1zIEopQ==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/intersect": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/interpolate": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-7.3.4.tgz", + "integrity": "sha512-lwYSMbHxsXYEWObv0tyBCjwTLXyfsTvOLn/NFhlsGrNCYEXn8I1VPtLGwuxbSdF3hVRgurn8qftkB1npHrNs6Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/hex-grid": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/point-grid": "7.3.4", + "@turf/square-grid": "7.3.4", + "@turf/triangle-grid": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-7.3.4.tgz", + "integrity": "sha512-VsqMEMeRWWs2mjwI7sTlUgH1cEfugTGhQ0nF8ncHG7YKd9HUUTzIKpn9FJeoguPWIYITcy1ar4yJEOU/hteBVw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.3.4.tgz", + "integrity": "sha512-88Eo4va4rce9sNZs6XiMJowWkikM3cS2TBhaCKlU+GFHdNf8PFEpiU42VDU8q5tOF6/fu21Rvlke5odgOGW4AQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isobands": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-7.3.4.tgz", + "integrity": "sha512-SFYefwjQdQfF0MV0zfaSwNg9J1wD7mfPP8scGcScKGM3admbwS2A3V8rqPADBfYLD2eCPBDFnySxcl9SHbPung==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.4", + "@turf/bbox": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/explode": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isolines": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-7.3.4.tgz", + "integrity": "sha512-UFRIULkIgkZOmrhLxExWvguixbzfoCgVcXIqo2Cp68do4v+nwc3pTM7MTt4DBVFloIdX0Usrn4K44LQ/V05gxg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/jsts": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@turf/jsts/-/jsts-2.7.2.tgz", + "integrity": "sha512-zAezGlwWHPyU0zxwcX2wQY3RkRpwuoBmhhNE9HY9kWhFDkCxZ3aWK5URKwa/SWKJbj9aztO+8vtdiBA28KVJFg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "dependencies": { + "jsts": "2.7.1" + } + }, + "node_modules/@turf/kinks": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-7.3.4.tgz", + "integrity": "sha512-LZTKELWxvXl0vc9ZxVgi0v07fO9+2FrZOam2B10fz/eGjy3oKNazU5gjggbnc499wEIcJS4hN+VyjQZrmsJAdQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-7.3.4.tgz", + "integrity": "sha512-Dg1GnQ/B2go5NIWXt91N4L7XTjIgIWCftBSYIXkrpIM7QGjItzglek0Z5caytvb8ZRWXzZOGs8//+Q5we91WuQ==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-arc": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-7.3.4.tgz", + "integrity": "sha512-nqZ+JKjDVIrvREFHgtJIP9Ps4WbWw3eStqdIzAPolrzoXyAZnpIKquyfRTxpJFYUUjDmf+uQ/SFWsPP4SOWAqQ==", + "license": "MIT", + "dependencies": { + "@turf/circle": "7.3.4", + "@turf/destination": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-chunk": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-7.3.4.tgz", + "integrity": "sha512-xWEHR99EpUO5ZPEZhMfa0QvnFZC0W+QLxB1GcJcSeJAQ5ZMXUXY8doKF1Nztk0eppawMprEEO3nQWLvQoR4z2g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/length": "7.3.4", + "@turf/line-slice-along": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-7.3.4.tgz", + "integrity": "sha512-XygbTvHa6A+v6l2ZKYtS8AAWxwmrPxKxfBbdH75uED1JvdytSLWYTKGlcU3soxd9sYb4x/g9sDvRIVyU6Lucrg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "sweepline-intersections": "^1.5.0", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-offset": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-7.3.4.tgz", + "integrity": "sha512-CSrg3njde9Tx+C0oL+BHUpZYpgD+PEmzp0ldDNis5ZQiTe5tUrwiIyG7A/QXf9eDnGhtV1WhCAycX0Wjged4pg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-overlap": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-7.3.4.tgz", + "integrity": "sha512-3GBECiwNAQ2MmSwiqAHMweIl+EiePK0Jx4fXxF1KFE+NGCDv/MbGcEYfAbmsTg8mg6oRI9D8fJZzrT44DHpHXA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-on-line": "7.3.4", + "@turf/geojson-rbush": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-segment": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/nearest-point-on-line": "7.3.4", + "@types/geojson": "^7946.0.10", + "fast-deep-equal": "^3.1.3", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-7.3.4.tgz", + "integrity": "sha512-UeISzf/JHoWEY5yeoyvKwA5epWcvJMCpCwbIMolvfTC5pp+IVozjHPVCRvRWuzmbmAvetcW0unL5bjqi0ADmuQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-7.3.4.tgz", + "integrity": "sha512-6Vt4Eptdr2C5T+jtpbo8D4v8b6X7KqYonPPyMB6huv+Kcg3nz4JRI9OQCDCaon9rWvU3ffWwjsjcbJCQS9o0sA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/nearest-point-on-line": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice-along": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-7.3.4.tgz", + "integrity": "sha512-RT5HydNy8+m9Y3u39USeYZauG2EyMqCYoLnTpWcAxbZGdq9WjIwdzAwYir3d8eJkOzjlR6Khz071VM4Ufqs0Kg==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.4", + "@turf/destination": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-split": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-7.3.4.tgz", + "integrity": "sha512-l1zmCSUnGsiN4gf22Aw91a2VnYs5DZS67FdkYqKgr+wPEAL/gpQgIBBWSTmhwY8zb3NEqty+f/gMEe8EJAWYng==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/geojson-rbush": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@turf/line-segment": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/nearest-point-on-line": "7.3.4", + "@turf/truncate": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-7.3.4.tgz", + "integrity": "sha512-vRnDHjzwOroC74/fsJEU+dUeGhiR/B2bG0/HeEWRBplAjmwVPptRBmDGtXKTz8sbA6or17/XtOITp3zTU0lBZw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/mask": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-7.3.4.tgz", + "integrity": "sha512-FJIlSk8m0AiqzNoLSMdYuhDRif6aeOYVdW/WxjEjpUoMalwy2w5MMlZqJB9zxt/xSrMq6lvTWJgZfZfGL2s4ZQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.3.4.tgz", + "integrity": "sha512-tlmw9/Hs1p2n0uoHVm1w3ugw1I6L8jv9YZrcdQa4SH5FX5UY0ATrKeIvfA55FlL//PGuYppJp+eyg/0eb4goqw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-7.3.4.tgz", + "integrity": "sha512-/XAeGvsz8l5HaqcP7TUlexzGfibqXozQgBZ8rH7az6op2Dfm3pL/Z7bKLHoVavM0ccBg0Pt7g6j9NM54kZWdKA==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.4", + "@turf/destination": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/moran-index": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-7.3.4.tgz", + "integrity": "sha512-SNb16szwEG0OiyNn3z9zvSnk3M3tfwvvN8i//9UIC32APEApI+MRXCl93H/qZkKMhhh/cHA0pF0pjYZwl5z8Ow==", + "license": "MIT", + "dependencies": { + "@turf/distance-weight": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-neighbor-analysis": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/nearest-neighbor-analysis/-/nearest-neighbor-analysis-7.3.4.tgz", + "integrity": "sha512-8EZlDy5poU0t7BDy8KTzOmfiGsAs2kWuB3/kgI4sMdbThKVk2P4hHKuToCSGvqAzwSy3B2qKYM1N6JeVWytu+w==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.4", + "@turf/bbox": "7.3.4", + "@turf/bbox-polygon": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/nearest-point": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-7.3.4.tgz", + "integrity": "sha512-WfI09f2bX0nKx/jkO7zCt3tUrJulyAlUYQtZHP7lWYMCOmZ6Pq26D6lKWjpfs2it0OHbhlx1XF/UupEUaz830w==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-7.3.4.tgz", + "integrity": "sha512-DQrP3lRju83rIXFN68tUEpc7ki/eRwdwBkK2CTT4RAcyCxbcH2NGJPQv8dYiww/Ar77u1WLVn+aINXZH904dWw==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-to-line": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-7.3.4.tgz", + "integrity": "sha512-Nzp3ojQt0gDACNYG+oNWymRXAUCey0LzdiSezYtRwdA0/+FQCtuxP8Lbc8FftV10JL8D78/CRlmt7omaXLLXCg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/point-to-line-distance": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/planepoint": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-7.3.4.tgz", + "integrity": "sha512-KAhMAnddbuWIEZuk2bK//g+xTeKn8aV9N2AaE27x6JMJyV/wqvatIuVVqEIXI3SkAFbhiVBpVuarvPYhrJ+fhg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-grid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-7.3.4.tgz", + "integrity": "sha512-9CL3OJ4dEt266+fxYlOQeRFqAY3XtsAuak2Gpk+K8k+Y3yGv8pvyn3QaAQ6P2npbiKt0zfG8Md/+HBAPOMPQ0A==", + "license": "MIT", + "dependencies": { + "@turf/boolean-within": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-on-feature": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-7.3.4.tgz", + "integrity": "sha512-tQfIxsJUxZqyO7OeJC25y3DqN9i4fmrAt4TBrPvZcIIwymgN7aMrElJKlg/dfi7JDihKp3h/CkWMjtMQA14Vwg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/center": "7.3.4", + "@turf/explode": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/nearest-point": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-7.3.4.tgz", + "integrity": "sha512-IdPAxlAQZj7FCZg+ObyVHlNdqwLL/oxYoQjpxMNJ511gNxokCtEv0aeRZQjYOYIxr9Ss97v3yo3ILJaF9V2kPw==", + "license": "MIT", + "dependencies": { + "@turf/bearing": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/nearest-point-on-line": "7.3.4", + "@turf/projection": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@turf/rhumb-distance": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-polygon-distance": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/point-to-polygon-distance/-/point-to-polygon-distance-7.3.4.tgz", + "integrity": "sha512-VxbkgHyzCkYWSxirqSUqw+lzbYmTf2qFhVZ/T5dprhwyXWcgalpupvgRzmZmjKkgsoJ017vrvCNKZRaCCn+Z7Q==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/point-to-line-distance": "7.3.4", + "@turf/polygon-to-line": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/points-within-polygon": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-7.3.4.tgz", + "integrity": "sha512-HfT83Iw99zywDfCp+nJwS+JDzH+GdNug0sut9WDjGEznHKoZyAcOk+hGKL/ja8TeCLx9VsZHOiVCQFm+NTgvgA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-smooth": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-7.3.4.tgz", + "integrity": "sha512-AnpaGgNYVvP/dfz10id3AotDrUh9O+4unXCk3es1ff51VrpUhVgH3H+zyTSbVL4zAXN/ejPb8UnKCxDvNOQs4g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-tangents": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-7.3.4.tgz", + "integrity": "sha512-D1IFocXJYF8PUMZ+BmnOstyRrzklqC86FgakYVk9O61F9Ki8LhMGaRfF+6reKMD473KvHvEf1M2EgmGt+OHDRw==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/boolean-within": "7.3.4", + "@turf/explode": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/nearest-point": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-to-line": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-7.3.4.tgz", + "integrity": "sha512-xhmOZ5rHZAKLUDLeYKWMsX84ip8CCGOcGLBHtPPYOjdIDHddMV6Sxt5kVgkmlZpK6NEWEmOD6lYR4obxHcHlGA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygonize": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-7.3.4.tgz", + "integrity": "sha512-kmj05rkJ4tE8LvbQ4GVsL5GOrRiX/F5W4RIdxo8gPGTw1Y5oLG/1vFk6Hg6x63L1WcdNtF0sq6AdEI0G9BXWXA==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/envelope": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.3.4.tgz", + "integrity": "sha512-p91zOaLmzoBHzU/2H6Ot1tOhTmAom85n1P7I4Oo0V9xU8hmJXWfNnomLFf/6rnkKDIFZkncLQIBz4iIecZ61sA==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/quadrat-analysis": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/quadrat-analysis/-/quadrat-analysis-7.3.4.tgz", + "integrity": "sha512-Yxqq8wgrDiXIX+s0uOZ2exmYfRwTIcUX8J7j4P+sbyLVbyN8W3AjN2s5ZX21P0aFf3v24FBd2fNWlm5VmMUAdg==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.4", + "@turf/bbox": "7.3.4", + "@turf/bbox-polygon": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/point-grid": "7.3.4", + "@turf/random": "7.3.4", + "@turf/square-grid": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/random": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-7.3.4.tgz", + "integrity": "sha512-CXMS5XDoI5x0zc1aCYbn3t603k8hjaFHNsSOvGBW20z68cwP0UwMQQr0KLqFPqI4J1O7dMX+urn8IHH27RXFYg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rectangle-grid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-7.3.4.tgz", + "integrity": "sha512-qM7vujJ4wndB4MKZlEcnUSawgvs5wXpSEFf4f+LWRIfmGhtv6serzDqFzWcmy8kF8hg5J465PMktRmAFWq/a+w==", + "license": "MIT", + "dependencies": { + "@turf/boolean-intersects": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-7.3.4.tgz", + "integrity": "sha512-4BZ8MHMujl4NAT7XnIs7JoOuDhpR96oDTB0RtqTeIP4onioIedVnw1ZA3Uq08sILGpR0qKLuDsvdz4x9jtbptg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-clockwise": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-7.3.4.tgz", + "integrity": "sha512-tvX1toSo80q0iL0cUMMXpSKsCCfOjRqDGCmOdR6B9shhk6xP1ZM2PLQDr+MFPBFeGyQuyY4CNFkV2+3DF49vYw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-destination": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-7.3.4.tgz", + "integrity": "sha512-6HikEb5nm2A18FQWk6vVLMQkc099I/7c69j47RYM27xQK8J8uBCNk1zLYyMPcZTh24xcNSbZ1iPHDsDOqw6wWQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-7.3.4.tgz", + "integrity": "sha512-phwskeijdgYMsR3qDQmytfsg2iZcp3uWK7UFc76wKTEpxozbDGFI4enX5gXvZPpyI1iD7gsktGqHsO33AjnFDA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sample": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-7.3.4.tgz", + "integrity": "sha512-XzAATg09c2XYAXkIBbg8lktSrU1tXNjJYXtbVwF6jLp1q2wTRpwb+mZpTEPAwzZwVF81uR5c0CsdQyr5UHINVw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sector": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-7.3.4.tgz", + "integrity": "sha512-x2tNAXl21HRcF302ghU5ohE/vmmfDcXpQKgoWHyi7o5Q9kDRBwy7kbvr5YxbT3vwW/kAWUDYM7FoXNH42bXgCw==", + "license": "MIT", + "dependencies": { + "@turf/circle": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/line-arc": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/shortest-path": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-7.3.4.tgz", + "integrity": "sha512-xbK/oM+JRL+lJCHkAdZ3QPgoivT40J9WKJ0d1Ddt8LXTpzX2YeJVgcwOZaBPG9ncZUzHfHIWS1rUjc54clnZcg==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/bbox-polygon": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/clean-coords": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/transform-scale": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/simplify": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-7.3.4.tgz", + "integrity": "sha512-OoSwu3vI0H9P+GzLDaOJIL9v0V8ubeP8wQjM8GeMEZrq6U2uh9JWQnAU+jviT3ODcKF5H+88snpiMik585L0wA==", + "license": "MIT", + "dependencies": { + "@turf/clean-coords": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-7.3.4.tgz", + "integrity": "sha512-vJ+NeiEaOVsb8YiUExtyIgvH+ZybthHszl2TASZn5q340ioKHPb2JeHGlbgrB2x8pEMh3MVhoqxAbXDuND/cnw==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-7.3.4.tgz", + "integrity": "sha512-MgjlVRklQYFfQm9yJNha9kXothLPliVdeycNdmn4lWLH3SOZe1rqJPB5Z9+dhmJELT3BJraDq3W5ik5taEpKyQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/rectangle-grid": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/standard-deviational-ellipse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-7.3.4.tgz", + "integrity": "sha512-+BaetOKN8zA2mQCVTcRWMcfidNR3JkjmYj0r5iGRncK0J+pdxIjX2q6sF6yBMOOxMoEMy393P7j07HdBIPbibw==", + "license": "MIT", + "dependencies": { + "@turf/center-mean": "7.3.4", + "@turf/ellipse": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/points-within-polygon": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tag": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-7.3.4.tgz", + "integrity": "sha512-ienLhLzBLeChtKhbJMmU3/vGg0hWzi6Wh/q0n39W4CmdNb+yAoGQhlYjcCbPOJT4IcdFlWE3OhbP9EmH/xPgfg==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tesselate": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-7.3.4.tgz", + "integrity": "sha512-NnDgVb5ZchJEhEpq1je2hktS5UhnHMfeeumxZQgnIoMeGILpJtcOL//b/1biBBUVSJ0ZZg5zxiHdQc1PgK2gxA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "earcut": "^2.2.4", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tin": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-7.3.4.tgz", + "integrity": "sha512-tuegrGlbKPp6Dm8r5SuYDtQ2EVzdXVVxelqI1agnzj9N+l8oTBIKLRxRbBkLsizeVIDnlmVHCQB6cRc3v+u8JQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-rotate": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-7.3.4.tgz", + "integrity": "sha512-pbUG6QLwyJvvitq4aAq4IQH79X8T0NmEPUGDUEEP69yW7t4+UZjDBAVbCKwpOc8gtsK0K5yvxlZ0e2CdtpNmEw==", + "license": "MIT", + "dependencies": { + "@turf/centroid": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@turf/rhumb-destination": "7.3.4", + "@turf/rhumb-distance": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-scale": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-7.3.4.tgz", + "integrity": "sha512-7gUIFFHaU3Ewj3rCzIu5Yo7Zjfv4R2ypjh6UWiMJnDavb7RQ8fn0AKKcNMA/vF/yxuncp2l3zoa2gygv4AKM8A==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.4", + "@turf/center": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@turf/rhumb-destination": "7.3.4", + "@turf/rhumb-distance": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-translate": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-7.3.4.tgz", + "integrity": "sha512-qbSIEueOR8mNB7p4EB88vHvUAyuSBM8zxP68UiiTNV3Gh+OZF2VXTFiu3EFYMTaD9sE6Lxmzvv3fjW8N2q82pw==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/rhumb-destination": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/triangle-grid": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-7.3.4.tgz", + "integrity": "sha512-0bki10XwYvNcPzDcSs5kUh3niOogdVeFtawJEz5FdlyTAUohbNlC+Vb40K//OqEyTrGII+q1/dE4q+1J6ZCmDA==", + "license": "MIT", + "dependencies": { + "@turf/distance": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/intersect": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/truncate": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-7.3.4.tgz", + "integrity": "sha512-VPXdae9+RLLM19FMrJgt7QANBikm7DxPbfp/dXgzE4Ca7v+mJ4T1fYc7gCZDaqOrWMccHKbvv4iSuW7YZWdIIA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/turf": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-7.3.4.tgz", + "integrity": "sha512-uMAKLYt2tWJj8xIepq4vExF1r8fzJviP/5l/elDHuRyauI2mASy/Gox6kSFlrN0t0p8AT4Cs8o//4GuJTXyC+Q==", + "license": "MIT", + "dependencies": { + "@turf/along": "7.3.4", + "@turf/angle": "7.3.4", + "@turf/area": "7.3.4", + "@turf/bbox": "7.3.4", + "@turf/bbox-clip": "7.3.4", + "@turf/bbox-polygon": "7.3.4", + "@turf/bearing": "7.3.4", + "@turf/bezier-spline": "7.3.4", + "@turf/boolean-clockwise": "7.3.4", + "@turf/boolean-concave": "7.3.4", + "@turf/boolean-contains": "7.3.4", + "@turf/boolean-crosses": "7.3.4", + "@turf/boolean-disjoint": "7.3.4", + "@turf/boolean-equal": "7.3.4", + "@turf/boolean-intersects": "7.3.4", + "@turf/boolean-overlap": "7.3.4", + "@turf/boolean-parallel": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/boolean-point-on-line": "7.3.4", + "@turf/boolean-touches": "7.3.4", + "@turf/boolean-valid": "7.3.4", + "@turf/boolean-within": "7.3.4", + "@turf/buffer": "7.3.4", + "@turf/center": "7.3.4", + "@turf/center-mean": "7.3.4", + "@turf/center-median": "7.3.4", + "@turf/center-of-mass": "7.3.4", + "@turf/centroid": "7.3.4", + "@turf/circle": "7.3.4", + "@turf/clean-coords": "7.3.4", + "@turf/clone": "7.3.4", + "@turf/clusters": "7.3.4", + "@turf/clusters-dbscan": "7.3.4", + "@turf/clusters-kmeans": "7.3.4", + "@turf/collect": "7.3.4", + "@turf/combine": "7.3.4", + "@turf/concave": "7.3.4", + "@turf/convex": "7.3.4", + "@turf/destination": "7.3.4", + "@turf/difference": "7.3.4", + "@turf/dissolve": "7.3.4", + "@turf/distance": "7.3.4", + "@turf/distance-weight": "7.3.4", + "@turf/ellipse": "7.3.4", + "@turf/envelope": "7.3.4", + "@turf/explode": "7.3.4", + "@turf/flatten": "7.3.4", + "@turf/flip": "7.3.4", + "@turf/geojson-rbush": "7.3.4", + "@turf/great-circle": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/hex-grid": "7.3.4", + "@turf/interpolate": "7.3.4", + "@turf/intersect": "7.3.4", + "@turf/invariant": "7.3.4", + "@turf/isobands": "7.3.4", + "@turf/isolines": "7.3.4", + "@turf/kinks": "7.3.4", + "@turf/length": "7.3.4", + "@turf/line-arc": "7.3.4", + "@turf/line-chunk": "7.3.4", + "@turf/line-intersect": "7.3.4", + "@turf/line-offset": "7.3.4", + "@turf/line-overlap": "7.3.4", + "@turf/line-segment": "7.3.4", + "@turf/line-slice": "7.3.4", + "@turf/line-slice-along": "7.3.4", + "@turf/line-split": "7.3.4", + "@turf/line-to-polygon": "7.3.4", + "@turf/mask": "7.3.4", + "@turf/meta": "7.3.4", + "@turf/midpoint": "7.3.4", + "@turf/moran-index": "7.3.4", + "@turf/nearest-neighbor-analysis": "7.3.4", + "@turf/nearest-point": "7.3.4", + "@turf/nearest-point-on-line": "7.3.4", + "@turf/nearest-point-to-line": "7.3.4", + "@turf/planepoint": "7.3.4", + "@turf/point-grid": "7.3.4", + "@turf/point-on-feature": "7.3.4", + "@turf/point-to-line-distance": "7.3.4", + "@turf/point-to-polygon-distance": "7.3.4", + "@turf/points-within-polygon": "7.3.4", + "@turf/polygon-smooth": "7.3.4", + "@turf/polygon-tangents": "7.3.4", + "@turf/polygon-to-line": "7.3.4", + "@turf/polygonize": "7.3.4", + "@turf/projection": "7.3.4", + "@turf/quadrat-analysis": "7.3.4", + "@turf/random": "7.3.4", + "@turf/rectangle-grid": "7.3.4", + "@turf/rewind": "7.3.4", + "@turf/rhumb-bearing": "7.3.4", + "@turf/rhumb-destination": "7.3.4", + "@turf/rhumb-distance": "7.3.4", + "@turf/sample": "7.3.4", + "@turf/sector": "7.3.4", + "@turf/shortest-path": "7.3.4", + "@turf/simplify": "7.3.4", + "@turf/square": "7.3.4", + "@turf/square-grid": "7.3.4", + "@turf/standard-deviational-ellipse": "7.3.4", + "@turf/tag": "7.3.4", + "@turf/tesselate": "7.3.4", + "@turf/tin": "7.3.4", + "@turf/transform-rotate": "7.3.4", + "@turf/transform-scale": "7.3.4", + "@turf/transform-translate": "7.3.4", + "@turf/triangle-grid": "7.3.4", + "@turf/truncate": "7.3.4", + "@turf/union": "7.3.4", + "@turf/unkink-polygon": "7.3.4", + "@turf/voronoi": "7.3.4", + "@types/geojson": "^7946.0.10", + "@types/kdbush": "^3.0.5", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/union": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-7.3.4.tgz", + "integrity": "sha512-JJYyPMmGcrTa9sPv2ief2QU9Hb//cEAU1zgKu/OfoCMa9a8Imp5QVm9UTAkhGlc+4qm/N/X16iJ+cvVWaxPjkg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/unkink-polygon": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-7.3.4.tgz", + "integrity": "sha512-dFIqTLAnLL5D3OANPJtRb5OvmOM81GlNCjwgjlLQy0xdpYgKwGdE+gNXjygDrPUUXNc22xnaj3EfAfC3Pq7W4Q==", + "license": "MIT", + "dependencies": { + "@turf/area": "7.3.4", + "@turf/boolean-point-in-polygon": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/meta": "7.3.4", + "@types/geojson": "^7946.0.10", + "rbush": "^3.0.1", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/voronoi": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-7.3.4.tgz", + "integrity": "sha512-cwKSiDzDHRnA7yafQ1zOhWxRuMzp+fYFFzadCdByBAG1jAD7UlFwKhS1fjNPBNs67Fl5X3LL5ahCLW5gEdFgmg==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.4", + "@turf/helpers": "7.3.4", + "@turf/invariant": "7.3.4", + "@types/d3-voronoi": "^1.1.12", + "@types/geojson": "^7946.0.10", + "d3-voronoi": "1.1.2", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3-voronoi": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.12.tgz", + "integrity": "sha512-DauBl25PKZZ0WVJr42a6CNvI6efsdzofl9sajqZr2Gf5Gu733WkDdUGiPkUHXiUvYGzNNlFQde2wdZdfQPG+yw==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/geokdbush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/geokdbush/-/geokdbush-1.1.5.tgz", + "integrity": "sha512-jIsYnXY+RQ/YCyBqeEHxYN9mh+7PqKJUJUp84wLfZ7T2kqyVPNaXwZuvf1A2uQUkrvVqEbsG94ff8jH32AlLvA==", + "license": "MIT", + "dependencies": { + "@types/kdbush": "^1" + } + }, + "node_modules/@types/geokdbush/node_modules/@types/kdbush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/kdbush/-/kdbush-1.0.7.tgz", + "integrity": "sha512-QM5iB8m/0mnGOjUKshErIZQ0LseyTieRSYc3yaOpmrRM0xbWiOuJUWlduJx+TPNK7/VFMWphUGwx3nus7eT1Wg==", + "license": "MIT" + }, + "node_modules/@types/kdbush": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/kdbush/-/kdbush-3.0.5.tgz", + "integrity": "sha512-tdJz7jaWFu4nR+8b2B+CdPZ6811ighYylWsu2hpsivapzW058yP0KdfZuNY89IiRe5jbKvBGXN3LQdN2KPXVdQ==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz", + "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/arc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/arc/-/arc-0.2.0.tgz", + "integrity": "sha512-8NFOo126uYKQJyXNSLY/jSklgfLQL+XWAcPXGo876JwEQ8nSOPXWNI3TV2jLZMN8QEw8uksJ1ZwS4npjBca8MA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "license": "ISC", + "dependencies": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==", + "license": "BSD-3-Clause" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/geojson-equality-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/geojson-equality-ts/-/geojson-equality-ts-1.0.2.tgz", + "integrity": "sha512-h3Ryq+0mCSN/7yLs0eDgrZhvc9af23o/QuC4aTiuuzP/MRCtd6mf5rLsLRY44jX0RPUfM8c4GqERQmlUxPGPoQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.14" + } + }, + "node_modules/geojson-polygon-self-intersections": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/geojson-polygon-self-intersections/-/geojson-polygon-self-intersections-1.2.2.tgz", + "integrity": "sha512-6XRNF4CsRHYmR9z5YuIk5f/aOototnDf0dgMqYGcS7y1l57ttt6MAIAxl3rXyas6lq1HEbTuLMh4PgvO+OV42w==", + "license": "MIT", + "dependencies": { + "rbush": "^2.0.1" + } + }, + "node_modules/geojson-polygon-self-intersections/node_modules/quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==", + "license": "ISC" + }, + "node_modules/geojson-polygon-self-intersections/node_modules/rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "license": "MIT", + "dependencies": { + "quickselect": "^1.0.1" + } + }, + "node_modules/geokdbush": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/geokdbush/-/geokdbush-2.0.1.tgz", + "integrity": "sha512-0M8so1Qx6+jJ1xpirpCNrgUsWAzIcQ3LrLmh0KJPBYI3gH7vy70nY5zEEjSp9Tn0nBt6Q2Fh922oL08lfib4Zg==", + "license": "ISC", + "dependencies": { + "tinyqueue": "^2.0.3" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jsts": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/jsts/-/jsts-2.7.1.tgz", + "integrity": "sha512-x2wSZHEBK20CY+Wy+BPE7MrFQHW6sIsdaGUMEqmGAio+3gFzQaBYPwLRonUfQf9Ak8pBieqj9tUofX1+WtAEIg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "engines": { + "node": ">= 12" + } + }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==", + "license": "MIT" + }, + "node_modules/point-in-polygon-hao": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.4.tgz", + "integrity": "sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/point-in-polygon-hao/node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/polyclip-ts": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/polyclip-ts/-/polyclip-ts-0.16.8.tgz", + "integrity": "sha512-JPtKbDRuPEuAjuTdhR62Gph7Is2BS1Szx69CFOO3g71lpJDFo78k4tFyi+qFOMVPePEzdSKkpGU3NBXPHHjvKQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.1.0", + "splaytree-ts": "^1.0.2" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, + "node_modules/random-location": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/random-location/-/random-location-1.1.3.tgz", + "integrity": "sha512-+9SZa8R0JTfjrdgImCyq4TJRDzGHfUM9qghsA3D7zrMnUaQVn/xIDtFI+yPegJGO7KuIkduNlL8JXqu3M7Au7w==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", + "license": "Unlicense" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==", + "license": "MIT" + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/splaytree-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/splaytree-ts/-/splaytree-ts-1.0.2.tgz", + "integrity": "sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA==", + "license": "BDS-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sweepline-intersections": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sweepline-intersections/-/sweepline-intersections-1.5.0.tgz", + "integrity": "sha512-AoVmx72QHpKtItPu72TzFL+kcYjd67BPLDoR0LarIk+xyaRg+pDTMFXndIEvZf9xEKnJv6JdhgRMnocoG0D3AQ==", + "license": "MIT", + "dependencies": { + "tinyqueue": "^2.0.0" + } + }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", + "license": "ISC" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "geo2topo": "bin/geo2topo" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "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/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/server/traque-back/package.json b/server/traque-back/package.json index f82221c..9467e86 100644 --- a/server/traque-back/package.json +++ b/server/traque-back/package.json @@ -12,6 +12,7 @@ "license": "ISC", "type": "module", "dependencies": { + "@turf/turf": "^7.3.4", "dotenv": "^16.4.5", "express": "^4.19.2", "multer": "^1.4.5-lts.1", diff --git a/server/traque-back/src/config/events.js b/server/traque-back/src/config/events.js new file mode 100644 index 0000000..21b454c --- /dev/null +++ b/server/traque-back/src/config/events.js @@ -0,0 +1,36 @@ +export const PLAYER_HANDLER_EVENTS = { + LOGIN: "login", + LOGOUT: "logout", + LOCATION: "location", + SCAN: "scan", + CAPTURE: "capture", + DEVICE: "device", +}; + +export const ADMIN_HANDLER_EVENTS = { + LOGIN: "login", + LOGOUT: "logout", + STATE: "state", + SETTINGS: "settings", + ADD_TEAM: "add-team", + REMOVE_TEAM: "remove-team", + REORDER_TEAM: "reorder-team", + ELIMINATE_TEAM: "eliminate-team", + REVIVE_TEAM: "revive-team", +}; + +export const GAME_MANAGER_EVENTS = { + INIT_PLAYER: "init-player", + INIT_ADMIN: "init-admin", + UPDATE_GAME: "update-game", + DELETE_TEAM: "delete-team", +}; + +export const PLAYER_SYNCHRONIZER_EVENTS = { + UPDATE_FULL: "update-full", + LOGOUT: "logout", +}; + +export const ADMIN_SYNCHRONIZER_EVENTS = { + UPDATE_FULL: "update-full", +}; diff --git a/server/traque-back/src/config/game.js b/server/traque-back/src/config/game.js new file mode 100644 index 0000000..a8f8e3e --- /dev/null +++ b/server/traque-back/src/config/game.js @@ -0,0 +1,44 @@ +import { DEFAULT_ZONES_SETTINGS } from "@/config/zone.js"; +import { DefaultState, PlacementState, PlayingState, FinishedState } from '@/core/states/game/index.js'; + +export const STATE_SETTINGS = { + ENTRY_STATE_CLASS: DefaultState, + TRANSITION_MATRIX: { + [DefaultState.name]: { + [DefaultState.name]: (state) => false, + [PlacementState.name]: (state) => state.canGameStart(), + [PlayingState.name]: (state) => false, + [FinishedState.name]: (state) => false, + }, + [PlacementState.name]: { + [DefaultState.name]: (state) => true, + [PlacementState.name]: (state) => false, + [PlayingState.name]: (state) => true, + [FinishedState.name]: (state) => false, + }, + [PlayingState.name]: { + [DefaultState.name]: (state) => true, + [PlacementState.name]: (state) => true, + [PlayingState.name]: (state) => false, + [FinishedState.name]: (state) => state.isGameOver(), + }, + [FinishedState.name]: { + [DefaultState.name]: (state) => true, + [PlacementState.name]: (state) => false, + [PlayingState.name]: (state) => false, + [FinishedState.name]: (state) => false, + } + }, +}; + +export const DEFAULT_GAME_SETTINGS = { + playingZones: DEFAULT_ZONES_SETTINGS.CIRCLE, + placementZones: {}, + scanDelay: 10 * 60 * 1000, // ms + outOfZoneDelay: 5 * 60 * 1000 // ms +} + +export const TEAM_ID_LENGTH = 6; +export const CAPTURE_CODE_LENGTH = 4; + +export const RESTART_TIMERS = true; diff --git a/server/traque-back/src/config/server.js b/server/traque-back/src/config/server.js new file mode 100644 index 0000000..7b5be81 --- /dev/null +++ b/server/traque-back/src/config/server.js @@ -0,0 +1,6 @@ +import { config } from "dotenv"; +config(); + +export const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH || ""; +export const HOST = process.env.HOST || "0.0.0.0"; +export const PORT = Number(process.env.PORT) || 3000; diff --git a/server/traque-back/src/config/zone.js b/server/traque-back/src/config/zone.js new file mode 100644 index 0000000..527caa0 --- /dev/null +++ b/server/traque-back/src/config/zone.js @@ -0,0 +1,13 @@ +export const ZONE_TYPES = { + CIRCLE: "circle", + POLYGON: "polygon" +} + +export const DEFAULT_ZONES_SETTINGS = { + CIRCLE: { type: ZONE_TYPES.CIRCLE, min: null, max: null, reductionCount: 5, duration: 20 * 60 * 1000 }, + POLYGON: { type: ZONE_TYPES.POLYGON, polygons: [] }, +}; + +export const TURF_DISTANCE_UNIT = 'meters'; +export const TURF_CIRCLE_STEPS = 32; +export const TURF_BUFFER_SIZE = 1; diff --git a/server/traque-back/src/core/factories/game_zone_factory.js b/server/traque-back/src/core/factories/game_zone_factory.js new file mode 100644 index 0000000..f229b50 --- /dev/null +++ b/server/traque-back/src/core/factories/game_zone_factory.js @@ -0,0 +1,70 @@ +import * as turf from '@turf/turf'; +import { ZoneWithDuration } from '@/core/models/zone.js'; +import { TURF_BUFFER_SIZE, TURF_CIRCLE_STEPS, TURF_DISTANCE_UNIT, ZONE_TYPES } from '@/config/zone.js'; + +export const settingsToZoneList = (settings) => { + if (!settings) return []; + + switch (settings.type) { + case ZONE_TYPES.CIRCLE: + return circleSettingsToZoneList(settings); + case ZONE_TYPES.POLYGON: + return polygonSettingsToZoneList(settings); + default: + return []; + } +}; + +const circleSettingsToZoneList = ({ min, max, reductionCount, duration }) => { + if (min == null || max == null) return []; + + const zones = []; + const add = (center, radius) => zones.push(new ZoneWithDuration(turf.circle(turf.point(center), radius, { steps: TURF_CIRCLE_STEPS, units: TURF_DISTANCE_UNIT }), duration)); + + // Add max zone + add(turf.point(max.center), max.radius); + + // Add intermediates zones + const radiusReductionLength = (max.radius - min.radius) / reductionCount; + let center = turf.point(max.center); + let radius = max.radius; + for (let i = 1; i < reductionCount; i++) { + radius -= radiusReductionLength; + + let tempCenter; + do { + const distance = radius * Math.sqrt(Math.random()); + const bearing = Math.random() * 360; + tempCenter = turf.destination(center, distance, bearing, { units: 'meters' }); + } while (turf.distance(tempCenter, turf.point(min.center)) > radius - min.radius); + center = tempCenter; + + add(center, radius); + } + + // Add min zone + add(turf.point(min.center), min.radius); + + return zones; +}; + +const polygonSettingsToZoneList = ({ polygons }) => { + const polygonsCount = polygons.length; + if (polygonsCount === 0) return []; + + // Convert polygons to turf polygon with a buffer + const bufferedPolygons = polygons.map(item => ({ + polygon: turf.buffer(turf.polygon([[...item.polygon, item.polygon[0]]]), TURF_BUFFER_SIZE, { units: TURF_DISTANCE_UNIT }), + duration: item.duration + })); + + const inversedZones = [bufferedPolygons[polygonsCount-1]]; + + for (let i = polygonsCount-2; 0 <= i; i--) { + const { polygon, duration } = bufferedPolygons[i]; + const union = turf.union(turf.featureCollection([inversedZones[inversedZones.length-1].polygon, polygon])); + if (union && union.geometry.type === 'Polygon') inversedZones.push(new ZoneWithDuration(union, duration)); + } + + return [...inversedZones].reverse(); +}; diff --git a/server/traque-back/src/core/factories/placement_zone_factory.js b/server/traque-back/src/core/factories/placement_zone_factory.js new file mode 100644 index 0000000..3cf9ddc --- /dev/null +++ b/server/traque-back/src/core/factories/placement_zone_factory.js @@ -0,0 +1,9 @@ +import * as turf from '@turf/turf'; +import { Zone } from '@/core/models/zone.js'; +import { TURF_CIRCLE_STEPS, TURF_DISTANCE_UNIT } from '@/config/zone.js'; + +export const settingsToZone = (settings) => { + if (!settings) return null; + const { center, radius } = settings; + return new Zone(turf.circle(turf.point(center), radius, { steps: TURF_CIRCLE_STEPS, units: TURF_DISTANCE_UNIT })); +}; diff --git a/server/traque-back/src/core/game_manager.js b/server/traque-back/src/core/game_manager.js deleted file mode 100644 index 3566937..0000000 --- a/server/traque-back/src/core/game_manager.js +++ /dev/null @@ -1,140 +0,0 @@ -import { Team } from "@/team/team.js"; -import { randint } from "@/util/util.js"; -import zoneManager from "./zone_manager.js" -import { EventEmitter } from 'events'; -import { EVENTS } from "@/socket/playerHandler.js"; -import { DefaultState } from "@/states/default_state.js"; -import { CircularMap } from "@/util/circular_map.js"; - - -const isTeamNameValide = (teamName) => { - if (typeof teamName !== 'string') return false; - if (teamName.length === 0) return false; - return true; -}; - -const getNewTeamId = (teams) => { - const idLength = 6; - let newTeamId; - do { - newTeamId = randint(10 ** idLength); - } while (teams.has(newTeamId)); - return newTeamId.toString().padStart(idLength, '0'); -}; - - - -class GameManager extends EventEmitter { - constructor() { - super(); - this.currentState = new DefaultState(this); - this.teams = new CircularMap(); - this.settings = { - zone: zoneManager.settings, - scanDelay: 10 * 60 * 1000, // ms - outOfZoneDelay: 5 * 60 * 1000 // ms - } - } - - - // State - - setState(StateClass) { - this.currentState.exit(); - this.currentState = new StateClass(this); - this.currentState.enter(); - } - - - // Settings - - setSettings(settings) { - // Zones - zoneManager.changeSettings(settings.zone); - this.settings.zone = zoneManager.settings; // TODO : not have two copies of the same object - // Delays - this.settings.scanDelay = settings.scanDelay; - this.settings.outOfZoneDelay = settings.outOfZoneDelay; - } - - - // Emits - - emitTeamUpdate(target, team) { - this.emit(EVENTS.INTERNAL.TEAM_UPDATE, target, this.currentState.getTeamMapForTeam(team)); - } - - emitLogout(target) { - this.emit(EVENTS.INTERNAL.LOGOUT, target); - } - - - // Actions - - //// Boilerplates - - _performOnTeam(actionName, teamId, ...args) { - if (!this.teams.has(teamId)) return false; - const team = this.teams.get(teamId); - return this.currentState[actionName](team, ...args); - } - - //// All states - - addTeam(teamName) { - if (!isTeamNameValide(teamName)) return false; - const teamId = getNewTeamId(this.teams); - const team = new Team(teamId, teamName); - this.teams.set(teamId, team); - this.currentState.initTeamContext(team); - this.currentState.onTeamOrderChange(); - return true; - } - - removeTeam(teamId) { - if (!this.teams.has(teamId)) return false; - this.emitLogout(teamId); - this.currentState.clearTeamContext(this.teams.get(teamId)); - this.teams.delete(teamId); - this.currentState.onTeamOrderChange(); - return true; - } - - reorderTeam(newTeamsOrder) { - if (!this.teams.reorder(newTeamsOrder)) return false; - this.currentState.onTeamOrderChange(); - return true; - } - - updateLocation(teamId, coords) { - return this._performOnTeam("updateLocation", teamId, coords); - } - - //// Playing state - - eliminate(teamId) { - return this._performOnTeam("eliminate", teamId); - } - - revive(teamId) { - return this._performOnTeam("revive", teamId); - } - - addHandicap(teamId) { - return this._performOnTeam("addHandicap", teamId); - } - - clearHandicap(teamId) { - return this._performOnTeam("clearHandicap", teamId); - } - - scan(teamId, coords) { - return this._performOnTeam("updateLocation", teamId, coords) && this._performOnTeam("scan", teamId); - } - - capture(teamId, captureCode) { - return this._performOnTeam("capture", teamId, captureCode); - } -} - -export const gameManager = new GameManager(); diff --git a/server/traque-back/src/core/managers/game_manager.js b/server/traque-back/src/core/managers/game_manager.js new file mode 100644 index 0000000..32bf1c6 --- /dev/null +++ b/server/traque-back/src/core/managers/game_manager.js @@ -0,0 +1,122 @@ +import { EventEmitter } from 'events'; +import { ZoneManager } from "@/core/managers/zone_manager.js"; +import { TeamManager } from '@/core/managers/team_manager.js'; +import { GAME_MANAGER_EVENTS } from "@/config/events.js"; + + +export class GameManager extends EventEmitter { + constructor(stateSettings, gameSettings) { + super(); + // Data + this.teams = new TeamManager(); + this.zoneManager = new ZoneManager(); + this.settings = gameSettings; + // State + this.state = null; + this.transitionMatrix = stateSettings.TRANSITION_MATRIX; + this.setState(stateSettings.ENTRY_STATE_CLASS); + } + + + // --------------- ACTIONS --------------- // + + // State + + setState(StateClass) { + if (!this._canTransitionTo(StateClass)) return; + this.state?.exit(); + this.state = new StateClass(this.teams, this.zoneManager); + this.state.enter(this.settings); + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + } + + // Settings + + setSettings(settings) { + this.settings = settings; + this.state.applySettings(settings); + this.zoneManager.updateZones(settings.playingZones); + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + } + + // Teams + + addTeam(teamName) { + const team = this.teams.add(teamName); + if (team == null) return false; + this.state.initTeam(team); + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + return true; + } + + removeTeam(teamId) { + if (!this.teams.has(teamId)) return false; + this.state.clearTeam(this.teams.get(teamId)); + this.teams.delete(teamId); + this.emit(GAME_MANAGER_EVENTS.DELETE_TEAM, teamId); + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + return true; + } + + reorderTeam(newTeamsOrder) { + if (!this.teams.reorder(newTeamsOrder)) return false; + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + return true; + } + + // Team state + + eliminate(teamId) { + return this._teamAction(teamId, "eliminate"); + } + + revive(teamId) { + return this._teamAction(teamId, "revive"); + } + + addHandicap(teamId) { + return this._teamAction(teamId, "addHandicap"); + } + + clearHandicap(teamId) { + return this._teamAction(teamId, "clearHandicap"); + } + + scan(teamId) { + return this._teamAction(teamId, "scan"); + } + + capture(teamId, captureCode) { + return this._teamAction(teamId, "capture", captureCode); + } + + updateLocation(teamId, coords) { + return this._teamAction(teamId, "updateLocation", coords); + } + + + // --------------- OTHER --------------- // + + // Login handlers + + onPlayerLogin(socketId, teamId) { + this.emit(GAME_MANAGER_EVENTS.INIT_PLAYER, socketId, teamId); + } + + onAdminLogin(socketId) { + this.emit(GAME_MANAGER_EVENTS.INIT_ADMIN, socketId); + } + + // Util + + _canTransitionTo(StateClass) { + return this.state === null || this.transitionMatrix[this.state.name][StateClass.name](this.state); + }; + + _teamAction(teamId, actionName, ...args) { + if (!this.teams.has(teamId) || typeof this.state[actionName] !== 'function') return false; + const success = this.state[actionName](this.teams.get(teamId), ...args); + this.emit(GAME_MANAGER_EVENTS.UPDATE_GAME); + return success; + } +} diff --git a/server/traque-back/src/core/managers/team_manager.js b/server/traque-back/src/core/managers/team_manager.js new file mode 100644 index 0000000..1a2da84 --- /dev/null +++ b/server/traque-back/src/core/managers/team_manager.js @@ -0,0 +1,59 @@ +import { Team } from "@/core/models/team.js"; + +export class TeamManager { + constructor() { + this._map = new Map(); + this.order = []; + } + + + // Read + + get size() { + return this._map.size; + } + + get(id) { + return this._map.get(id); + } + + has(id) { + return this._map.has(id); + } + + forEach(callback) { + for (const id of this.order) { + callback(this._map.get(id), id, this); + } + } + + + // Write + + add(teamName) { + if (!Team.isTeamNameValid(teamName)) return null; + let id; do { id = Team.getNewTeamId() } while (this.has(id)); + const team = new Team(id, teamName); + if (!this.has(id)) this.order.push(id); + this._map.set(id, team); + return id; + } + + delete(id) { + if (!this._map.delete(id)) return false; + this.order = this.order.filter(i => i !== id); + return true; + } + + clear() { + this.order = []; + this._map.clear(); + } + + reorder(newOrder) { + const isValid = newOrder.length === this.size && new Set([...this.order, ...newOrder]).size === this.size; + if (!isValid) return false; + this.order = newOrder; + return true; + } +} diff --git a/server/traque-back/src/core/managers/zone_manager.js b/server/traque-back/src/core/managers/zone_manager.js new file mode 100644 index 0000000..1e8de1b --- /dev/null +++ b/server/traque-back/src/core/managers/zone_manager.js @@ -0,0 +1,73 @@ +import { Scheduler } from "@/util/scheduler.js"; +import { settingsToZoneList } from "@/core/factories/game_zone_factory.js"; + +export class ZoneManager { + constructor() { + this._zones = []; + this._currentZoneId = null; + this._scheduledZoneTransition = new Scheduler(); + } + + + get firstZonePolygon() { + return this._zones[0]?.polygon ?? null; + } + + get currentZonePolygon() { + return this._currentZone?.polygon ?? null; + } + + get nextZonePolygon() { + if (!this._isActive) return null; + return this._zones[this._currentZoneId + 1]?.polygon ?? null; + } + + get dateOfZoneTransition() { + return this._scheduledZoneTransition.dateOfExecution; + } + + get timeToZoneTransition() { + return this._scheduledZoneTransition.timeToExecution; + } + + start() { + this._jumpToNextZone(); + } + + stop() { + this._currentZoneId = null; + this._scheduledZoneTransition.interrupt(); + } + + isInZone(location) { + return this._isZonesEmpty || this._currentZone?.isInZone(location); + } + + updateZones(settings) { + this._zones = settingsToZoneList(settings); + if (this._isActive) { + this.stop(); + this.start(); + } + } + + + get _currentZone() { + if (!this._isActive) return null; + return this._zones[this._currentZoneId].polygon; + } + + get _isZonesEmpty() { + return this._zones.length === 0; + } + + get _isActive() { + return this._currentZoneId !== null; + } + + _jumpToNextZone() { + if (this._isZonesEmpty) return; + this._currentZoneId = this._isActive ? this._currentZoneId + 1 : 0; + if (this._currentZoneId + 1 < this._zones.length) this._scheduledZoneTransition.start(() => this._jumpToNextZone(), this._currentZone.duration); + } +} diff --git a/server/traque-back/src/core/models/team.js b/server/traque-back/src/core/models/team.js new file mode 100644 index 0000000..dce10e5 --- /dev/null +++ b/server/traque-back/src/core/models/team.js @@ -0,0 +1,32 @@ +import { CAPTURE_CODE_LENGTH, TEAM_ID_LENGTH } from "@/config/game.js"; +import { randint } from "@/util/random.js"; + +export class Team { + constructor(id, teamName) { + this.id = id; + this.name = teamName; + this.location = { coords: null, timestamp: null }; + this.state = null; + } + + static isTeamNameValid = (teamName) => { + return typeof teamName === 'string' && teamName.length > 0; + } + + static getNewTeamId = () => { + return randint(10 ** TEAM_ID_LENGTH).toString().padStart(TEAM_ID_LENGTH, '0'); + } + + static getNewCaptureCode = () => { + return randint(10 ** CAPTURE_CODE_LENGTH).toString().padStart(CAPTURE_CODE_LENGTH, '0'); + } + + equals(team) { + return this.id === team.id; + } + + updateLocation(coords) { + this.location = { coords: coords, timestamp: Date.now() }; + return true; + } +} diff --git a/server/traque-back/src/core/models/zone.js b/server/traque-back/src/core/models/zone.js new file mode 100644 index 0000000..d483643 --- /dev/null +++ b/server/traque-back/src/core/models/zone.js @@ -0,0 +1,28 @@ +import * as turf from '@turf/turf'; + +export class Zone { + constructor(turfPolygon) { + this._turfPolygon = turfPolygon; + } + + get polygon() { + // Return a [latitude, longitude] list + return this._turfPolygon.geometry.coordinates[0].slice(0, -1); + } + + isInZone(location) { + // location : [latitude, longitude] + return turf.booleanPointInPolygon(turf.point(location), this._turfPolygon); + } + + equals(zone) { + return turf.booleanEqual(this._turfPolygon, zone._turfPolygon); + } +}; + +export class ZoneWithDuration extends Zone { + constructor(turfPolygon, duration) { + super(turfPolygon); + this.duration = duration; + } +}; diff --git a/server/traque-back/src/core/states/game/default_state.js b/server/traque-back/src/core/states/game/default_state.js new file mode 100644 index 0000000..b7e33b3 --- /dev/null +++ b/server/traque-back/src/core/states/game/default_state.js @@ -0,0 +1,44 @@ +import { DefaultTeam } from "@/core/states/teams/default_team.js"; + +export class DefaultState { + constructor(teams, zoneManager) { + this.teams = teams; + this.zoneManager = zoneManager; + } + + static get name () { + return "default"; + } + + + // --------------- LIFE CYCLE --------------- // + + initTeam(team, settings) { + team.state = new DefaultTeam(team).init(settings); + } + + enter(settings) { + this.teams.forEach(team => this.initTeam(team, settings)); + } + + clearTeam(_team) {} + + exit() { + this.teams.forEach(team => this.clearTeam(team)); + } + + + // --------------- ACTIONS --------------- // + + updateLocation(team, coords) { + team.updateLocation(coords); + return true; + } + + + // --------------- OTHER --------------- // + + canGameStart() { + return this.teams.size >= 3; + } +} diff --git a/server/traque-back/src/core/states/game/finished_state.js b/server/traque-back/src/core/states/game/finished_state.js new file mode 100644 index 0000000..2a6a73c --- /dev/null +++ b/server/traque-back/src/core/states/game/finished_state.js @@ -0,0 +1,37 @@ +import { FinishedTeam } from "@/core/states/teams/finished_team.js"; + +export class FinishedState { + constructor(teams, zoneManager) { + this.teams = teams; + this.zoneManager = zoneManager; + } + + static get name () { + return "finished"; + } + + + // --------------- LIFE CYCLE --------------- // + + initTeam(team, settings) { + team.state = new FinishedTeam(team).init(settings); + } + + enter(settings) { + this.teams.forEach(team => this.initTeam(team, settings)); + } + + clearTeam(_team) {} + + exit() { + this.teams.forEach(team => this.clearTeam(team)); + } + + + // --------------- ACTIONS --------------- // + + updateLocation(team, coords) { + team.updateLocation(coords); + return true; + } +} diff --git a/server/traque-back/src/core/states/game/index.js b/server/traque-back/src/core/states/game/index.js new file mode 100644 index 0000000..110d8ee --- /dev/null +++ b/server/traque-back/src/core/states/game/index.js @@ -0,0 +1,4 @@ +export { DefaultState } from '@/core/states/game/default_state.js'; +export { PlacementState } from '@/core/states/game/placement_state.js'; +export { PlayingState } from '@/core/states/game/playing_state.js'; +export { FinishedState } from '@/core/states/game/finished_state.js'; diff --git a/server/traque-back/src/core/states/game/placement_state.js b/server/traque-back/src/core/states/game/placement_state.js new file mode 100644 index 0000000..b783183 --- /dev/null +++ b/server/traque-back/src/core/states/game/placement_state.js @@ -0,0 +1,42 @@ +import { PlacementTeam } from "@/core/states/teams/placement_team.js"; + +export class PlacementState { + constructor(teams, zoneManager) { + this.teams = teams; + this.zoneManager = zoneManager; + } + + static get name () { + return "placement"; + } + + + // --------------- LIFE CYCLE --------------- // + + initTeam(team, settings) { + team.state = new PlacementTeam(team).init(settings); + } + + enter(settings) { + this.teams.forEach(team => this.initTeam(team, settings)); + } + + clearTeam(_team) {} + + exit() { + this.teams.forEach(team => this.clearTeam(team)); + } + + + // --------------- ACTIONS --------------- // + + applySettings(settings) { + this.teams.forEach(team => team.state.applySettings(settings)); + return true; + } + + updateLocation(team, coords) { + team.updateLocation(coords); + return true; + } +} diff --git a/server/traque-back/src/core/states/game/playing_state.js b/server/traque-back/src/core/states/game/playing_state.js new file mode 100644 index 0000000..44c7da9 --- /dev/null +++ b/server/traque-back/src/core/states/game/playing_state.js @@ -0,0 +1,93 @@ +import { PlayingTeam } from "@/core/states/teams/playing_team.js"; + +export class PlayingState { + constructor(teams, zoneManager) { + this.teams = teams; + this.zoneManager = zoneManager; + } + + static get name () { + return "playing"; + } + + + // --------------- LIFE CYCLE --------------- // + + initTeam(team, settings) { + team.state = new PlayingTeam(team, this.zoneManager).init(settings); + } + + enter(settings) { + this.teams.forEach(team => this.initTeam(team, settings)); + this.zoneManager.start(); + } + + clearTeam(team) { + team.state.clear(); + } + + exit() { + this.teams.forEach(team => this.clearTeam(team)); + this.zoneManager.stop(); + } + + + // --------------- ACTIONS --------------- // + + applySettings(settings) { + this.teams.forEach(team => team.state.applySettings(settings)); + return true; + } + + eliminate(team) { + return team.state.eliminate(); + } + + revive(team) { + return team.state.revive(); + } + + addHandicap(team) { + return team.state.addHandicap(); + } + + clearHandicap(team) { + return team.state.clearHandicap(); + } + + scan(team) { + return team.state.scan(this.getTarget(team)); + } + + capture(team, captureCode) { + return team.state.capture(this.getTarget(team), captureCode); + } + + updateLocation(team, coords) { + team.updateLocation(coords); + return team.state.updateIsInZone(); + } + + + // --------------- OTHER --------------- // + + get _playingTeams() { + return this.teams.order.filter(team => !team.state.isEliminated); + } + + getHunter(team) { + const length = this._playingTeams.length; + const i = this.teams.order.indexOf(team.id); + return this._playingTeams[(i+length-1) % length]; + } + + getTarget(team) { + const length = this._playingTeams.length; + const i = this.teams.order.indexOf(team.id); + return this._playingTeams[(i+1) % length]; + } + + isGameOver() { + return this._playingTeams.length <= 2; + } +} diff --git a/server/traque-back/src/core/states/teams/default_team.js b/server/traque-back/src/core/states/teams/default_team.js new file mode 100644 index 0000000..07d3276 --- /dev/null +++ b/server/traque-back/src/core/states/teams/default_team.js @@ -0,0 +1,14 @@ +export class DefaultTeam { + constructor(team) { + this.team = team; + } + + + // --------------- LIFE CYCLE --------------- // + + init(_settings) { + return this; + } + + clear() {} +} diff --git a/server/traque-back/src/core/states/teams/finished_team.js b/server/traque-back/src/core/states/teams/finished_team.js new file mode 100644 index 0000000..36678b1 --- /dev/null +++ b/server/traque-back/src/core/states/teams/finished_team.js @@ -0,0 +1,14 @@ +export class FinishedTeam { + constructor(team) { + this.team = team; + } + + + // --------------- LIFE CYCLE --------------- // + + init(_settings) { + return this; + } + + clear() {} +} diff --git a/server/traque-back/src/core/states/teams/placement_team.js b/server/traque-back/src/core/states/teams/placement_team.js new file mode 100644 index 0000000..203c864 --- /dev/null +++ b/server/traque-back/src/core/states/teams/placement_team.js @@ -0,0 +1,25 @@ +import { settingsToZone } from "@/core/factories/placement_zone_factory.js"; + +export class PlacementTeam { + constructor(team) { + this.team = team; + } + + + // --------------- LIFE CYCLE --------------- // + + init(settings) { + this.placementZone = settingsToZone(settings.placementZones[this.team.id]); + return this; + } + + clear() {} + + + // --------------- ACTIONS --------------- // + + applySettings(settings) { + this.placementZone = settingsToZone(settings.placementZones[this.team.id]); + return true; + } +} diff --git a/server/traque-back/src/core/states/teams/playing_team.js b/server/traque-back/src/core/states/teams/playing_team.js new file mode 100644 index 0000000..be40678 --- /dev/null +++ b/server/traque-back/src/core/states/teams/playing_team.js @@ -0,0 +1,100 @@ +import { Team } from "@/core/models/team.js"; +import { ScheduledTask } from "@/util/scheduler.js"; +import { RESTART_TIMERS } from "@/config/game.js"; + +export class PlayingTeam { + constructor(team, zoneManager) { + this.team = team; + this.zoneManager = zoneManager; + } + + + // --------------- LIFE CYCLE --------------- // + + init(settings) { + // Team + this.scanLocation = this.team.location; + this.captureCode = Team.getNewCaptureCode(); + // Booleans + this.isEliminated = false; + this.isOutOfZone = false; + this.hasHandicap = false; + // Scheduled taks + this.scheduledScan = new ScheduledTask(() => this.scan(), settings.scanDelay).start(); + this.scheduledHandicap = new ScheduledTask(() => this.addHandicap(), settings.outOfZoneDelay); + // Target + this.targetScanLocation = { coords: null, timesptamp: null }; + return this; + } + + clear() { + this.scheduledScan.interrupt(); + this.scheduledHandicap.interrupt(); + } + + + // --------------- ACTIONS --------------- // + + applySettings(settings) { + this.scheduledScan.setDelay(settings.scanDelay, RESTART_TIMERS); + this.scheduledHandicap.setDelay(settings.outOfZoneDelay, RESTART_TIMERS); + return true; + } + + eliminate() { + if (this.isEliminated) return false; + this.clear(); + this.isEliminated = true; + return true; + } + + revive() { + if (!this.isEliminated) return false; + this.init(); + return true; + } + + addHandicap() { + if (this.hasHandicap) return false; + this.hasHandicap = true; + this.scheduledScan.interrupt(); + return true; + } + + clearHandicap() { + if (!this.hasHandicap) return false; + this.hasHandicap = false; + this.scheduledScan.start(); + return true; + } + + scan(target) { + if (this.hasHandicap || this.isEliminated) return false; + this.scanLocation = this.team.location; + this.targetScanLocation = target.state.scanLocation; + this.scheduledScan.start(); + return true; + } + + capture(target, captureCode) { + if (this.hasHandicap || this.isEliminated) return false; + if (captureCode != target.state.captureCode) return false; + target.state.eliminate(); + return true; + } + + updateLocation() { + const isOutOfZone = !this.zoneManager.isInZone(this.team.location); + // Exit zone case + if (isOutOfZone && !this.isOutOfZone) { + this.isOutOfZone = true; + this.scheduledHandicap.start(); + // Enter zone case + } else if (!isOutOfZone && this.isOutOfZone) { + this.isOutOfZone = false; + this.scheduledHandicap.interrupt(); + this.clearHandicap(); + } + return true; + } +} diff --git a/server/traque-back/src/core/zone_manager.js b/server/traque-back/src/core/zone_manager.js deleted file mode 100644 index beeb1ad..0000000 --- a/server/traque-back/src/core/zone_manager.js +++ /dev/null @@ -1,242 +0,0 @@ -import { haversineDistance, EARTH_RADIUS } from "./util.js"; - - -/* -------------------------------- Useful functions and constants -------------------------------- */ - -const ZONE_TYPES = { - CIRCLE: "circle", - POLYGON: "polygon" -} - -function latlngEqual(latlng1, latlng2, epsilon = 1e-9) { - return Math.abs(latlng1.lat - latlng2.lat) < epsilon && Math.abs(latlng1.lng - latlng2.lng) < epsilon; -} - - -/* -------------------------------- Circle zones -------------------------------- */ - -const defaultCircleSettings = {type: ZONE_TYPES.CIRCLE, min: null, max: null, reductionCount: 4, duration: 10} - -function circleZone(center, radius, duration) { - return { - type: ZONE_TYPES.CIRCLE, - center: center, - radius: radius, - duration: duration, - - isInZone(location) { - return haversineDistance(center, location) < this.radius; - } - } -} - -function circleSettingsToZones(settings) { - const {min, max, reductionCount, duration} = settings; - - if (!min || !max) return []; - if (haversineDistance(max.center, min.center) > max.radius - min.radius) return []; - - const zones = [circleZone(max.center, max.radius, duration)]; - const radiusReductionLength = (max.radius - min.radius) / reductionCount; - let center = max.center; - let radius = max.radius; - - for (let i = 1; i < reductionCount; i++) { - radius -= radiusReductionLength; - let new_center = null; - while (!new_center || haversineDistance(new_center, min.center) > radius - min.radius) { - const angle = Math.random() * 2 * Math.PI; - const angularDistance = Math.sqrt(Math.random()) * radiusReductionLength / EARTH_RADIUS; - const lat0Rad = center.lat * Math.PI / 180; - const lon0Rad = center.lng * Math.PI / 180; - const latRad = Math.asin( - Math.sin(lat0Rad) * Math.cos(angularDistance) + - Math.cos(lat0Rad) * Math.sin(angularDistance) * Math.cos(angle) - ); - - const lonRad = lon0Rad + Math.atan2( - Math.sin(angle) * Math.sin(angularDistance) * Math.cos(lat0Rad), - Math.cos(angularDistance) - Math.sin(lat0Rad) * Math.sin(latRad) - ); - new_center = {lat: latRad * 180 / Math.PI, lng: lonRad * 180 / Math.PI}; - } - center = new_center; - zones.push(circleZone(center, radius, duration)) - } - zones.push(circleZone(min.center, min.radius, 0)); - - return zones; -} - - -/* -------------------------------- Polygon zones -------------------------------- */ - -const defaultPolygonSettings = {type: ZONE_TYPES.POLYGON, polygons: []} - -function polygonZone(polygon, duration) { - return { - type: ZONE_TYPES.POLYGON, - polygon: polygon, - duration: duration, - - isInZone(location) { - const {lat: x, lng: y} = location; - let inside = false; - - for (let i = 0, j = this.polygon.length - 1; i < this.polygon.length; j = i++) { - const {lat: xi, lng: yi} = this.polygon[i]; - const {lat: xj, lng: yj} = this.polygon[j]; - - const intersects = ((yi > y) !== (yj > y)) && (x < ((xj - xi) * (y - yi)) / (yj - yi) + xi); - - if (intersects) inside = !inside; - } - - return inside; - } - } -} - -function mergePolygons(poly1, poly2) { - // poly1 and poly2 are clockwise, not overlaping and touching polygons. If those two polygons were on a map, they would be - // one against each other, and the merge would make a new clockwise polygon out of the outer border of the two polygons. - // If it happens that poly1 and poly2 are not touching, poly1 would be returned untouched. - // Basically because polygons are clockwise, the alogorithm starts from a point A in poly1 not shared by poly2, and - // when a point is shared by poly1 and poly2, the algorithm continues in poly2, and so on until point A. - - const getPointIndex = (point, array) => { - for (let i = 0; i < array.length; i++) { - if (latlngEqual(array[i], point)) return i; - } - return -1; - } - - // Find the index of the first point of poly1 that doesn't belong to merge (it exists) - let i = 0; - while (getPointIndex(poly1[i], poly2) != -1) i++; - // Starting the merge from that point - const merge = [poly1[i]]; - i = (i + 1) % poly1.length; - let currentArray = poly1; - let otherArray = poly2; - while (!latlngEqual(currentArray[i], merge[0])) { - const j = getPointIndex(currentArray[i], otherArray); - if (j != -1) { - [currentArray, otherArray] = [otherArray, currentArray]; - i = j; - } - merge.push(currentArray[i]); - i = (i + 1) % currentArray.length; - } - return merge; -} - -function polygonSettingsToZones(settings) { - const {polygons} = settings; - - const zones = []; - - for (const { polygon, duration } of polygons.slice().reverse()) { - const length = zones.length; - - if (length == 0) { - zones.push(polygonZone( - polygon, - duration - )); - } else { - zones.push(polygonZone( - mergePolygons(zones[length-1].polygon, polygon), - duration - )); - } - } - - return zones.slice().reverse(); -} - - -/* -------------------------------- Zone manager -------------------------------- */ - -export default { - isRunning: false, - zones: [], // A zone has to be connected space that doesn't contain an earth pole - currentZone: null, - settings: defaultPolygonSettings, - - start() { - if (this.isRunning) return; - this.isRunning = true; - this.currentZone = { id: -1, timeoutId: null, endDate: null }; - this.goNextZone(); - }, - - stop() { - if (!this.isRunning) return; - clearTimeout(this.currentZone.timeoutId); - this.isRunning = false; - this.currentZone = null; - }, - - goNextZone() { - if (!this.isRunning) return; - this.currentZone.id++; - if (this.currentZone.id >= this.zones.length - 1) { - this.currentZone.endDate = Date.now(); - } else { - this.currentZone.timeoutId = setTimeout(() => this.goNextZone(), this.getCurrentZone().duration * 60 * 1000); - this.currentZone.endDate = Date.now() + this.getCurrentZone().duration * 60 * 1000; - } - this.zoneBroadcast(); - }, - - getCurrentZone() { - if (!this.isRunning) return null; - return this.zones[this.currentZone.id]; - }, - - getNextZone() { - if (!this.isRunning) return null; - if (this.currentZone.id + 1 < this.zones.length) { - return this.zones[this.currentZone.id + 1]; - } else { - return this.zones[this.currentZone.id]; - } - }, - - isInZone(location) { - if (!this.isRunning) return false; - if (this.zones.length == 0) { - return true; - } else { - return this.getCurrentZone().isInZone(location); - } - }, - - changeSettings(settings) { - switch (settings.type) { - case ZONE_TYPES.CIRCLE: - this.zones = circleSettingsToZones(settings); - break; - case ZONE_TYPES.POLYGON: - this.zones = polygonSettingsToZones(settings); - break; - default: - this.zones = []; - break; - } - this.settings = settings; - this.stop(); - this.start(); - this.zoneBroadcast(); - }, - - zoneBroadcast() { - if (!this.isRunning) return; - const zone = { - begin: this.getCurrentZone(), - end: this.getNextZone(), - endDate:this.currentZone.endDate, - }; - }, -} diff --git a/server/traque-back/src/externals/api/photo.js b/server/traque-back/src/externals/api/photo.js new file mode 100644 index 0000000..8da8f1b --- /dev/null +++ b/server/traque-back/src/externals/api/photo.js @@ -0,0 +1,65 @@ +import multer from "multer"; +import fs from "fs"; +import path from "path"; + +export class PhotoService { + constructor(gameManager) { + this.gameManager = gameManager; + this.uploadDir = path.join(process.cwd(), "teams_photos"); + this.missingImage = path.join(process.cwd(), "assets", "images", "missing_image.jpg"); + this.allowedMime = ["image/png", "image/jpeg", "image/gif"]; + } + + _initStorage() { + if (fs.existsSync(this.uploadDir)) fs.rmSync(this.uploadDir, { recursive: true }); + fs.mkdirSync(this.uploadDir); + } + + _setupMulter() { + const storage = multer.diskStorage({ + destination: (req, file, cb) => cb(null, this.uploadDir), + filename: (req, file, cb) => cb(null, `${req.query.team}`) + }); + + this.upload = multer({ + storage, + fileFilter: (req, file, cb) => { + const isAllowed = this.allowedMime.includes(file.mimetype); + const teamExists = this.gameManager.teams.has(req.query.team); + cb(null, isAllowed && teamExists); + } + }); + } + + _sendTeamImage(res, imageId) { + const imagePath = path.join(this.uploadDir, imageId); + res.set({ + "Content-Type": "image/png", + "Access-Control-Allow-Origin": "*" + }); + res.sendFile(fs.existsSync(imagePath) ? imagePath : this.missingImage); + } + + init(app) { + this._initStorage(); + this._setupMulter(); + + app.post("/upload", this.upload.single('file'), (req, res) => { + res.set("Access-Control-Allow-Origin", "*").send(""); + }); + + app.get("/photo/my", (req, res) => { + const team = this.gameManager.teams.get(req.query.team); + if (!team) return res.status(400).send("Team not found"); + this._sendTeamImage(res, team.id); + }); + + app.get("/photo/enemy", (req, res) => { + const team = this.gameManager.teams.get(req.query.team); + if (!team) return res.status(400).send("Team not found"); + this._sendTeamImage(res, team.target.id); + }); + + return this; + } +} diff --git a/server/traque-back/src/externals/handlers/adminHandler.js b/server/traque-back/src/externals/handlers/adminHandler.js new file mode 100644 index 0000000..d8d5655 --- /dev/null +++ b/server/traque-back/src/externals/handlers/adminHandler.js @@ -0,0 +1,79 @@ +import { createHash } from "crypto"; +import { ADMIN_PASSWORD_HASH } from "@/config/server.js"; +import { ADMIN_HANDLER_EVENTS } from "@/config/events.js"; + +export class AdminHandler { + constructor(gameManager) { + this.gameManager = gameManager; + } + + init(io) { + io.on("connection", (socket) => { + console.log("Connection of an admin"); + new AdminConnection(socket, this.gameManager); + }); + + return this; + } +} + +class AdminConnection { + constructor(socket, gameManager) { + this._socket = socket; + this._gameManager = gameManager; + this._isLoggedIn = false; + this._setupListeners(); + } + + _login(password) { + if (this._isLoggedIn) return; + + const hash = createHash('sha256').update(password).digest('hex'); + if (hash !== ADMIN_PASSWORD_HASH) return false; + + this._isLoggedIn = true; + this._gameManager.onAdminLogin(this._socket.id); + } + + _logout() { + if (!this._isLoggedIn) return; + this._isLoggedIn = false; + } + + _setupListeners() { + + // Authentication + + this._socket.on("disconnect", () => { + console.log("Disconnection of an admin"); + this._logout() + }); + + this._socket.on(ADMIN_HANDLER_EVENTS.LOGIN, (password, callback) => { + this._login(password); + callback(this._isLoggedIn); + }); + + this._socket.on(ADMIN_HANDLER_EVENTS.LOGOUT, () => { + this._logout() + }); + + // Actions + + const protectedActions = { + [ADMIN_HANDLER_EVENTS.ADD_TEAM]: (id) => this._gameManager.addTeam(id), + [ADMIN_HANDLER_EVENTS.REMOVE_TEAM]: (id) => this._gameManager.removeTeam(id), + [ADMIN_HANDLER_EVENTS.REORDER_TEAM]: (id) => this._gameManager.reorderTeam(id), + [ADMIN_HANDLER_EVENTS.ELIMINATE_TEAM]: (id) => this._gameManager.eliminate(id), + [ADMIN_HANDLER_EVENTS.REVIVE_TEAM]: (id) => this._gameManager.revive(id), + [ADMIN_HANDLER_EVENTS.STATE]: (state) => this._gameManager.setState(state), + [ADMIN_HANDLER_EVENTS.SETTINGS]: (settings) => this._gameManager.setSettings(settings), + }; + + Object.entries(protectedActions).forEach(([event, action]) => { + this._socket.on(event, (data) => { + if (this._isLoggedIn) action(data); + }); + }); + } +} diff --git a/server/traque-back/src/externals/handlers/playerHandler.js b/server/traque-back/src/externals/handlers/playerHandler.js new file mode 100644 index 0000000..eb9a5b8 --- /dev/null +++ b/server/traque-back/src/externals/handlers/playerHandler.js @@ -0,0 +1,80 @@ +import { PLAYER_HANDLER_EVENTS } from "@/config/events.js"; + +export class PlayerHandler { + constructor(gameManager) { + this.gameManager = gameManager; + } + + init(io) { + io.on("connection", (socket) => { + console.log("Connection of a player"); + new PlayerConnection(socket, this.gameManager); + }); + + return this; + } +} + +class PlayerConnection { + constructor(socket, gameManager) { + this._socket = socket; + this._gameManager = gameManager; + this._teamId = null; + this._setupListeners(); + } + + _isLoggedIn() { + return this._teamId !== null; + } + + _login(loginTeamId) { + if (!this._gameManager.teams.has(loginTeamId) || this._teamId === loginTeamId) return; + this._logout(); + this._teamId = loginTeamId; + this._socket.join(this._teamId); + this._gameManager.onPlayerLogin(this._socket.id, this._teamId); + } + + _logout() { + if (!this._isLoggedIn()) return; + this._socket.leave(this._teamId); + this._teamId = null; + } + + _setupListeners() { + + // Authentication + + this._socket.on("disconnect", () => { + console.log("Disconnection of a player"); + this._logout() + }); + + this._socket.on(PLAYER_HANDLER_EVENTS.LOGIN, (loginTeamId, callback) => { + this._login(loginTeamId); + callback(this._isLoggedIn()); + }); + + this._socket.on(PLAYER_HANDLER_EVENTS.LOGOUT, () => { + this._logout() + }); + + // Actions + + this._socket.on(PLAYER_HANDLER_EVENTS.LOCATION, (coords) => { + if (this._isLoggedIn()) return; + this._gameManager.updateLocation(this._teamId, coords); + }); + + this._socket.on(PLAYER_HANDLER_EVENTS.SCAN, (coords) => { + if (this._isLoggedIn()) return; + this._gameManager.updateLocation(this._teamId, coords); + this._gameManager.scan(this._teamId); + }); + + this._socket.on(PLAYER_HANDLER_EVENTS.CAPTURE, (captureCode, callback) => { + if (this._isLoggedIn()) return; + callback(this._gameManager.capture(this._teamId, captureCode)); + }); + } +} diff --git a/server/traque-back/src/externals/mappers/admin_mapper.js b/server/traque-back/src/externals/mappers/admin_mapper.js new file mode 100644 index 0000000..7618bb3 --- /dev/null +++ b/server/traque-back/src/externals/mappers/admin_mapper.js @@ -0,0 +1,69 @@ +import { DefaultState, PlacementState, PlayingState, FinishedState } from "@/core/states/game/index.js"; + +const TEAM_STATE_MAP = { + [DefaultState.name]: (_team, _gameState) => ({}), + + [PlacementState.name]: (team, _gameState) => ({ + placementZone: team.state.placementZone?.polygon ?? null, + isInPlacementZone: team.state.placementZone?.isInZone(team.location) ?? true, + }), + + [PlayingState.name]: (team, gameState) => ({ + // Team + captureCode: team.state.captureCode, + scanLocation: team.state.scanLocation, + // Booleans + isEliminated: team.state.isEliminated, + isOutOfZone: team.state.isOutOfZone, + hasHandicap: team.state.hasHandicap, + // Scheduled taks + scanDate: team.state.scheduledScan.dateOfExecution, + handicapDate: team.state.scheduledHandicap.dateOfExecution, + // Target and hunter + hunterName: gameState.getHunter(team).name, + targetName: gameState.getTarget(team).name, + targetScanLocation: team.state.targetScanLocation, + }), + + [FinishedState.name]: (_team, _gameState) => ({}), +}; + + +export class AdminMapper { + constructor(gameManager) { + this.gameManager = gameManager; + } + + map() { + const stateName = this.gameManager.state.name; + + const teamsDto = {}; + this.gameManager.teams.forEach((team, teamId) => { + teamsDto[teamId] = { + id: team.id, + name: team.name, + location: team.location, + state: TEAM_STATE_MAP[stateName](team, this.gameManager.state) + }; + }); + + const zonesDto = { + firstZone: this.gameManager.zoneManager.firstZonePolygon, + currentZone: this.gameManager.zoneManager.currentZonePolygon, + nextZone: this.gameManager.zoneManager.nextZonePolygon, + zoneTransitionDate: this.gameManager.zoneManager.dateOfZoneTransition + } + + return { + stateName: stateName, + teams: teamsDto, + teamsOrder: this.gameManager.teams.order, + zones: zonesDto, + settings: this.gameManager.settings + } + } + + hash(dto) { + return JSON.stringify(dto); + } +}; diff --git a/server/traque-back/src/externals/mappers/player_mapper.js b/server/traque-back/src/externals/mappers/player_mapper.js new file mode 100644 index 0000000..a415be5 --- /dev/null +++ b/server/traque-back/src/externals/mappers/player_mapper.js @@ -0,0 +1,54 @@ +import { DefaultState, PlacementState, PlayingState, FinishedState } from "@/core/states/game/index.js"; + +const TEAM_STATE_MAP = { + [DefaultState.name]: (_team, _gameState) => ({}), + + [PlacementState.name]: (team, _gameState) => ({ + placementZone: team.state.placementZone?.polygon ?? null, + isInPlacementZone: team.state.placementZone?.isInZone(team.location) ?? true, + playingZone: team.state.zoneManager.firstZonePolygon, + }), + + [PlayingState.name]: (team, gameState) => ({ + // Team + captureCode: team.state.captureCode, + scanLocation: team.state.scanLocation, + // Booleans + isEliminated: team.state.isEliminated, + isOutOfZone: team.state.isOutOfZone, + hasHandicap: team.state.hasHandicap, + // Scheduled taks + scanDate: team.state.scheduledScan.dateOfExecution, + handicapDate: team.state.scheduledHandicap.dateOfExecution, + // Target + targetName: gameState.getTarget(team).name, + targetHasHandicap: gameState.getTarget(team).state.hasHandicap, + targetScanLocation: team.state.targetScanLocation, + // Game zone + currentZone: team.state.zoneManager.currentZonePolygon, + nextZone: team.state.zoneManager.nextZonePolygon, + zoneTransitionDate: team.state.zoneManager.dateOfZoneTransition + }), + + [FinishedState.name]: (_team, _gameState) => ({}), +}; + +export class PlayerMapper { + constructor(gameState, team) { + this.gameState = gameState; + this.team = team; + } + + map() { + return { + id: this.team.id, + name: this.team.name, + stateName: this.gameState.name, + state: TEAM_STATE_MAP[this.gameState.name](this.team, this.gameState) + }; + } + + hash(dto) { + return JSON.stringify(dto); + } +}; diff --git a/server/traque-back/src/externals/synchronizers/admin_synchronizer.js b/server/traque-back/src/externals/synchronizers/admin_synchronizer.js new file mode 100644 index 0000000..ed3d88f --- /dev/null +++ b/server/traque-back/src/externals/synchronizers/admin_synchronizer.js @@ -0,0 +1,24 @@ +import { AdminMapper } from "@/externals/mappers/admin_mapper.js"; +import { StateTracker } from "@/util/state_tracker.js"; +import { GAME_MANAGER_EVENTS, ADMIN_SYNCHRONIZER_EVENTS } from "@/config/events.js"; + +export class AdminSynchronizer { + constructor(gameManager) { + this.gameManager = gameManager; + this.gameStateTracker = new StateTracker(new AdminMapper(this.gameManager)); + } + + init(io) { + this.gameManager.on(GAME_MANAGER_EVENTS.INIT_ADMIN, (socketId) => { + const { dto } = this.gameStateTracker.getSyncDto(); + io.to(socketId).emit(ADMIN_SYNCHRONIZER_EVENTS.UPDATE_FULL, dto); + }); + + this.gameManager.on(GAME_MANAGER_EVENTS.UPDATE_GAME, () => { + const { dto, hasChanged } = this.gameStateTracker.getSyncDto(); + if (hasChanged) io.emit(ADMIN_SYNCHRONIZER_EVENTS.UPDATE_FULL, dto); + }); + + return this; + } +} diff --git a/server/traque-back/src/externals/synchronizers/player_synchronizer.js b/server/traque-back/src/externals/synchronizers/player_synchronizer.js new file mode 100644 index 0000000..3a0c2e5 --- /dev/null +++ b/server/traque-back/src/externals/synchronizers/player_synchronizer.js @@ -0,0 +1,39 @@ +import { GAME_MANAGER_EVENTS, PLAYER_SYNCHRONIZER_EVENTS } from "@/config/events.js"; +import { PlayerMapper } from "@/externals/mappers/player_mapper.js"; +import { StateTracker } from "@/util/state_tracker.js"; + +export class PlayerSynchronizer { + constructor(gameManager) { + this.gameManager = gameManager; + this.teamsStateTracker = new Map(); + } + + init(io) { + this.gameManager.on(GAME_MANAGER_EVENTS.INIT_PLAYER, (socketId, teamId) => { + const { dto } = this._getSyncDtoOfTeam(this.gameManager.teams.get(teamId)); + io.to(socketId).emit(PLAYER_SYNCHRONIZER_EVENTS.UPDATE_FULL, dto); + }); + + this.gameManager.on(GAME_MANAGER_EVENTS.UPDATE_GAME, () => { + this.gameManager.teams.forEach((team, teamId) => { + const { dto, hasChanged } = this._getSyncDtoOfTeam(team); + if (hasChanged) io.to(teamId).emit(PLAYER_SYNCHRONIZER_EVENTS.UPDATE_FULL, dto); + }) + }); + + this.gameManager.on(GAME_MANAGER_EVENTS.DELETE_TEAM, (teamId) => { + this.teamsStateTracker.delete(teamId); + io.to(teamId).emit(PLAYER_SYNCHRONIZER_EVENTS.LOGOUT); + }); + + return this; + } + + _getSyncDtoOfTeam(team) { + if (!this.teamsStateTracker.has(team.id)) { + const mapper = new PlayerMapper(this.gameManager.state, team); + this.teamsStateTracker.set(team.id, new StateTracker(mapper)); + } + return this.teamsStateTracker.get(team.id).getSyncDto(); + } +} diff --git a/server/traque-back/src/index.js b/server/traque-back/src/index.js index d91b149..b66087d 100644 --- a/server/traque-back/src/index.js +++ b/server/traque-back/src/index.js @@ -1,24 +1,40 @@ import { createServer } from "http"; import express from "express"; import { Server } from "socket.io"; -import { initAdminSocketHandler } from "@/socket/adminHandler.js"; -import { initPlayerSocketHandler } from "@/socket/playerHandler.js"; -import { initPhotoUpload } from "./services/photo.js"; -import { PORT, HOST } from "@/util/util.js"; +// Core +import { GameManager } from "@/core/managers/game_manager.js"; +// Externals +import { PhotoService } from "@/externals/api/photo.js"; +import { PlayerSynchronizer } from "@/externals/synchronizers/player_synchronizer.js"; +import { PlayerHandler } from "@/externals/handlers/playerHandler.js"; +import { AdminSynchronizer } from "@/externals/synchronizers/admin_synchronizer.js"; +import { AdminHandler } from "@/externals/handlers/adminHandler.js"; +// Config +import { PORT, HOST } from "@/config/server.js"; +import { DEFAULT_GAME_SETTINGS, STATE_SETTINGS } from "@/config/game.js"; -// --- Configuration --- + +// Configuration const app = express(); const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: "*", methods: ["GET", "POST"] } }); -// --- Initialization --- -initPhotoUpload(app); -initAdminSocketHandler(io); -initPlayerSocketHandler(io); -// --- Server Start --- +// Initialization +const gameManager = new GameManager(STATE_SETTINGS, DEFAULT_GAME_SETTINGS); + +new PhotoService(gameManager).init(app); + +new PlayerHandler(gameManager).init(io.of("player")); +new AdminHandler(gameManager).init(io.of("admin")); + +new PlayerSynchronizer(gameManager).init(io.of("player")); +new AdminSynchronizer(gameManager).init(io.of("admin")); + + +// Server start httpServer.listen(PORT, HOST, () => { console.log(`Server running on http://${HOST}:${PORT}`); }); diff --git a/server/traque-back/src/old/admin_socket.js b/server/traque-back/src/old/admin_socket.js deleted file mode 100644 index 55c4334..0000000 --- a/server/traque-back/src/old/admin_socket.js +++ /dev/null @@ -1,98 +0,0 @@ -import { createHash } from "crypto"; -import { config } from "dotenv"; -import game from "./game.js" -import zoneManager from "./zone_manager.js" - -config(); - -const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH; - -export function secureAdminBroadcast(event, data) { - loggedInSockets.forEach(s => { - io.of("admin").to(s).emit(event, data); - }); -} - -let loggedInSockets = []; - -export function initAdminSocketHandler(io) { - io.of("admin").on("connection", (socket) => { - console.log("Connection of an admin"); - let loggedIn = false; - - const login = (password) => { - if (loggedIn) return false; - if (createHash('sha256').update(password).digest('hex') !== ADMIN_PASSWORD_HASH) return false; - loggedInSockets.push(socket.id); - loggedIn = true; - return true; - } - - const logout = () => { - if (!loggedIn) return false; - loggedInSockets = loggedInSockets.filter(s => s !== socket.id); - loggedIn = false; - return true; - } - - socket.on("disconnect", () => { - console.log("Disconnection of an admin"); - logout(); - }); - - socket.on("logout", () => { - logout(); - }); - - socket.on("login", (password) => { - if (!login(password)) return; - socket.emit("teams", game.teams); - socket.emit("game_state", { - state: game.state, - date: game.startDate - }); - socket.emit("current_zone", { - begin: zoneManager.getCurrentZone(), - end: zoneManager.getNextZone(), - endDate: zoneManager.currentZone?.endDate, - }); - socket.emit("settings", game.getAdminSettings()); - socket.emit("login_response", true); - }); - - socket.on("add_team", (teamName) => { - if (!loggedIn) return; - game.addTeam(teamName); - }); - - socket.on("remove_team", (teamId) => { - if (!loggedIn) return; - game.removeTeam(teamId); - }); - - socket.on("reorder_teams", (newOrder) => { - if (!loggedIn) return; - game.reorderTeams(newOrder); - }); - - socket.on("capture_team", (teamId) => { - if (!loggedIn) return; - game.switchCapturedTeam(teamId); - }); - - socket.on("placement_team", (teamId, placementZone) => { - if (!loggedIn) return; - game.placementTeam(teamId, placementZone); - }); - - socket.on("change_state", (state) => { - if (!loggedIn) return; - game.setState(state); - }); - - socket.on("update_settings", (settings) => { - if (!loggedIn) return; - game.changeSettings(settings); - }); - }); -} diff --git a/server/traque-back/src/old/game.js b/server/traque-back/src/old/game.js deleted file mode 100644 index b087df4..0000000 --- a/server/traque-back/src/old/game.js +++ /dev/null @@ -1,504 +0,0 @@ -import { secureAdminBroadcast } from "./admin_socket.js"; -import { teamBroadcast, playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"; -import { sendPositionTimeouts, outOfZoneTimeouts } from "./timeout_handler.js"; -import zoneManager from "./zone_manager.js"; -import trajectory from "./trajectory.js"; - -function randint(max) { - return Math.floor(Math.random() * max); -} - -function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) { - const degToRad = (deg) => deg * (Math.PI / 180); - var R = 6371; // Radius of the earth in km - var dLat = degToRad(lat2 - lat1); - var dLon = degToRad(lon2 - lon1); - var a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * - Math.sin(dLon / 2) * Math.sin(dLon / 2) - ; - var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - var d = R * c; // Distance in km - return d * 1000; -} - -function isInCircle(position, center, radius) { - return getDistanceFromLatLon(position, center) < radius; -} - -export const GameState = { - SETUP: "setup", - PLACEMENT: "placement", - PLAYING: "playing", - FINISHED: "finished" -} - -export default { - // List of teams, as objects. To see the fields see the addTeam method - teams: [], - // Current state of the game - state: GameState.SETUP, - // Date since the state changed - startDate: null, - // Messages - messages: { - waiting: "", - captured: "", - winner: "", - loser: "", - }, - - - - /* ------------------------------- USEFUL FUNCTIONS ------------------------------- */ - - getNewTeamId() { - let id = randint(1_000_000); - while (this.teams.find(t => t.id === id)) id = randint(1_000_000); - return id.toString().padStart(6, '0'); - }, - - checkEndGame() { - if (this.teams.filter(team => !team.captured).length <= 2) this.setState(GameState.FINISHED); - }, - - updateChasingChain() { - const playingTeams = this.teams.filter(team => !team.captured); - - for (let i = 0; i < playingTeams.length; i++) { - playingTeams[i].chasing = playingTeams[(i+1) % playingTeams.length].id; - playingTeams[i].chased = playingTeams[(playingTeams.length + i-1) % playingTeams.length].id; - sendUpdatedTeamInformations(playingTeams[i].id); - } - }, - - initLastSentLocations() { - const dateNow = Date.now(); - // Update of lastSentLocation - for (const team of this.teams) { - team.lastSentLocation = team.currentLocation; - team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; - sendPositionTimeouts.set(team.id); - sendUpdatedTeamInformations(team.id); - } - // Update of enemyLocation now we have the lastSentLocation of the enemy - for (const team of this.teams) { - team.enemyLocation = this.getTeam(team.chasing)?.lastSentLocation; - sendUpdatedTeamInformations(team.id); - } - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - }, - - resetTeamsInfos() { - for (const team of this.teams) { - // Chasing - team.captured = false; - team.chasing = null; - team.chased = null; - // Locations - team.lastSentLocation = null; - team.locationSendDeadline = null; - team.enemyLocation = null; - // Placement - team.ready = false; - // Zone - team.outOfZone = false; - team.outOfZoneDeadline = null; - team.hasHandicap = false; - // Stats - team.distance = 0; - team.nCaptures = 0; - team.nSentLocation = 0; - team.nObserved = 0; - team.finishDate = null; - sendUpdatedTeamInformations(team.id); - } - this.updateChasingChain(); - secureAdminBroadcast("teams", this.teams); - }, - - - - /* ------------------------------- STATE AND SETTINGS FUNCTIONS ------------------------------- */ - - getAdminSettings() { - return { - messages: this.messages, - zone: zoneManager.settings, - sendPositionDelay: sendPositionTimeouts.delay, - outOfZoneDelay: outOfZoneTimeouts.delay - }; - }, - - getPlayerSettings() { - return { - messages: this.messages, - zone: {type: zoneManager.settings.type}, - sendPositionDelay: sendPositionTimeouts.delay, - outOfZoneDelay: outOfZoneTimeouts.delay - }; - }, - - changeSettings(newSettings) { - if ("messages" in newSettings) this.messages = {...this.messages, ...newSettings.messages}; - if ("zone" in newSettings) zoneManager.changeSettings(newSettings.zone); - if ("sendPositionDelay" in newSettings) sendPositionTimeouts.setDelay(newSettings.sendPositionDelay); - if ("outOfZoneDelay" in newSettings) outOfZoneTimeouts.setDelay(newSettings.outOfZoneDelay); - // Broadcast new infos - secureAdminBroadcast("settings", this.getAdminSettings()); - playersBroadcast("settings", this.getPlayerSettings()); - }, - - setState(newState) { - const dateNow = Date.now(); - if (newState == this.state) return true; - switch (newState) { - case GameState.SETUP: - trajectory.stop(); - zoneManager.stop(); - sendPositionTimeouts.clearAll(); - outOfZoneTimeouts.clearAll(); - this.resetTeamsInfos(); - this.startDate = null; - break; - case GameState.PLACEMENT: - if (this.state == GameState.FINISHED || this.teams.length < 3) { - secureAdminBroadcast("game_state", {state: this.state, date: this.startDate}); - return false; - } - trajectory.stop(); - zoneManager.stop(); - sendPositionTimeouts.clearAll(); - outOfZoneTimeouts.clearAll(); - this.startDate = null; - break; - case GameState.PLAYING: - if (this.state == GameState.FINISHED || this.teams.length < 3) { - secureAdminBroadcast("game_state", {state: this.state, date: this.startDate}); - return false; - } - trajectory.start(); - zoneManager.start(); - this.initLastSentLocations(); - this.startDate = dateNow; - break; - case GameState.FINISHED: - if (this.state != GameState.PLAYING) { - secureAdminBroadcast("game_state", {state: this.state, date: this.startDate}); - return false; - } - trajectory.stop(); - zoneManager.stop(); - sendPositionTimeouts.clearAll(); - outOfZoneTimeouts.clearAll(); - this.teams.forEach(team => {if (!team.finishDate) team.finishDate = dateNow}); - secureAdminBroadcast("teams", this.teams); - break; - } - // Update the state - this.state = newState; - // Broadcast new infos - secureAdminBroadcast("game_state", {state: newState, date: this.startDate}); - playersBroadcast("game_state", {state: newState, date: this.startDate}); - return true; - }, - - - - /* ------------------------------- MANAGE PLAYERS FUNCTIONS ------------------------------- */ - - addPlayer(teamId, socketId) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Variables - const team = this.getTeam(teamId); - // Add the player - team.sockets.push(socketId); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - return true; - }, - - removePlayer(teamId, socketId) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Variables - const team = this.getTeam(teamId); - // Remove the player and its data - if (this.isPlayerCapitain(teamId, socketId)) { - team.battery = null; - team.phoneModel = null; - team.phoneName = null; - } - team.sockets = team.sockets.filter((sid) => sid != socketId); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - return true; - }, - - isPlayerCapitain(teamId, socketId) { - return this.getTeam(teamId).sockets.indexOf(socketId) == 0; - }, - - - - /* ------------------------------- MANAGE TEAMS FUNCTIONS ------------------------------- */ - - getTeam(teamId) { - return this.teams.find(t => t.id === teamId); - }, - - hasTeam(teamId) { - return this.teams.some(t => t.id === teamId); - }, - - addTeam(teamName) { - this.teams.push({ - // Identification - sockets: [], - name: teamName, - id: this.getNewTeamId(), - captureCode: randint(10_000), - // Chasing - captured: false, - chasing: null, - chased: null, - // Locations - lastSentLocation: null, - locationSendDeadline: null, - currentLocation: null, - lastCurrentLocationDate: null, - enemyLocation: null, - // Placement - startingArea: null, - ready: false, - // Zone - outOfZone: false, - outOfZoneDeadline: null, - hasHandicap: false, - // Stats - distance: 0, - nCaptures: 0, - nSentLocation: 0, - nObserved: 0, - finishDate: null, - // First socket infos - phoneModel: null, - phoneName: null, - battery: null, - }); - this.updateChasingChain(); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - return true; - }, - - removeTeam(teamId) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Logout the team - teamBroadcast(teamId, "logout"); - this.teams = this.teams.filter(t => t.id !== teamId); - sendPositionTimeouts.clear(teamId); - outOfZoneTimeouts.clear(teamId); - this.updateChasingChain(); - this.checkEndGame(); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - return true; - }, - - updateTeam(teamId, newInfos) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Update - this.teams = this.teams.map(team => team.id == teamId ? {...team, ...newInfos} : team); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(teamId); - return true; - }, - - switchCapturedTeam(teamId) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Variables - const team = this.getTeam(teamId); - const dateNow = Date.now(); - // Switch team.captured - if (this.state != GameState.PLAYING) return false; - if (team.captured) { - team.captured = false; - team.finishDate = null; - team.lastSentLocation = team.currentLocation; - team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; - sendPositionTimeouts.set(team.id); - } else { - team.captured = true; - team.finishDate = dateNow; - team.chasing = null; - team.chased = null; - sendPositionTimeouts.clear(team.id); - outOfZoneTimeouts.clear(team.id); - } - this.updateChasingChain(); - this.checkEndGame(); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - return true; - }, - - placementTeam(teamId, placementZone) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Variables - const team = this.getTeam(teamId); - // Make the capture - team.startingArea = placementZone; - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - return true; - }, - - reorderTeams(newOrder) { - // Update teams - const teamMap = new Map(this.teams.map(team => [team.id, team])); - this.teams = newOrder.map(id => teamMap.get(id)); - this.updateChasingChain(); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - return true; - }, - - handicapTeam(teamId) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - // Variables - const team = this.getTeam(teamId); - // Make the capture - team.hasHandicap = true; - sendPositionTimeouts.clear(team.id); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - return true; - }, - - - - /* ------------------------------- PLAYERS ACTIONS FUNCTIONS ------------------------------- */ - - updateLocation(teamId, location) { - // Test of parameters - if (!this.hasTeam(teamId)) return false; - if (!this.hasTeam(this.getTeam(teamId).chasing)) return false; - if (!location) return false; - // Variables - const team = this.getTeam(teamId); - const enemyTeam = this.getTeam(team.chasing); - const dateNow = Date.now(); - // Update distance - if (this.state == GameState.PLAYING && team.currentLocation) { - team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]})); - } - // Update of currentLocation - team.currentLocation = location; - team.lastCurrentLocationDate = dateNow; - // If hasHandicap - if (this.state == GameState.PLAYING && team.hasHandicap) { - team.lastSentLocation = team.currentLocation; - team.enemyLocation = enemyTeam.currentLocation; - } - // Update of ready - if (this.state == GameState.PLACEMENT && team.startingArea) { - team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius); - } - // Update out of zone - if (this.state == GameState.PLAYING) { - const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] }) - if (teamCurrentlyOutOfZone && !team.outOfZone) { - team.outOfZone = true; - team.outOfZoneDeadline = dateNow + outOfZoneTimeouts.delay * 60 * 1000; - outOfZoneTimeouts.set(teamId); - } else if (!teamCurrentlyOutOfZone && team.outOfZone) { - team.outOfZone = false; - team.outOfZoneDeadline = null; - team.hasHandicap = false; - if (!sendPositionTimeouts.has(team.id)) { - team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; - sendPositionTimeouts.set(team.id); - } - outOfZoneTimeouts.clear(teamId); - } - } - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - // Update of events of the game - trajectory.writePosition(dateNow, team.id, location[0], location[1]); - return true; - }, - - sendLocation(teamId) { - // Conditions - if (this.state != GameState.PLAYING) return false; - // Test of parameters - if (!this.hasTeam(teamId)) return false; - if (!this.hasTeam(this.getTeam(teamId).chasing)) return false; - // Variables - const team = this.getTeam(teamId); - const enemyTeam = this.getTeam(team.chasing); - const dateNow = Date.now(); - // Update team - team.nSentLocation++; - team.lastSentLocation = team.currentLocation; - team.enemyLocation = enemyTeam.lastSentLocation; - team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; - sendPositionTimeouts.set(team.id); - // Update enemy - enemyTeam.nObserved++; - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - sendUpdatedTeamInformations(enemyTeam.id); - // Update of events of the game - trajectory.writeSeePosition(dateNow, team.id, enemyTeam.id); - return true; - }, - - tryCapture(teamId, captureCode) { - // Conditions - if (this.state != GameState.PLAYING) return false; - // Test of parameters - if (!this.hasTeam(teamId)) return false; - if (!this.hasTeam(this.getTeam(teamId).chasing)) return false; - // Variables - const team = this.getTeam(teamId); - const enemyTeam = this.getTeam(team.chasing); - const dateNow = Date.now(); - // Verify the capture - if (enemyTeam.captureCode != captureCode) return false; - // Make the capture - team.nCaptures++; - enemyTeam.captured = true; - enemyTeam.finishDate = dateNow; - enemyTeam.chasing = null; - enemyTeam.chased = null; - sendPositionTimeouts.clear(enemyTeam.id); - outOfZoneTimeouts.clear(enemyTeam.id); - this.updateChasingChain(); - this.checkEndGame(); - // Broadcast new infos - secureAdminBroadcast("teams", this.teams); - sendUpdatedTeamInformations(team.id); - sendUpdatedTeamInformations(enemyTeam.id); - // Update of events of the game - trajectory.writeCapture(dateNow, team.id, enemyTeam.id); - return true; - }, -} diff --git a/server/traque-back/src/old/team_socket.js b/server/traque-back/src/old/team_socket.js deleted file mode 100644 index 5cc4520..0000000 --- a/server/traque-back/src/old/team_socket.js +++ /dev/null @@ -1,146 +0,0 @@ -/* -This file manages team access to the server via websocket. -It receives messages, checks permissions, manages authentication and performs actions by calling functions from other modules. -This module also exposes functions to send messages via socket to all teams -*/ -import game from "./game.js"; -import zoneManager from "./zone_manager.js"; - -/** - * Send a socket message to all the players of a team - * @param {String} teamId The team that will receive the message - * @param {String} event Event name - * @param {*} data The payload - */ -export function teamBroadcast(teamId, event, data) { - game.getTeam(teamId).sockets.forEach(socketId => io.of("player").to(socketId).emit(event, data)); -} - -/** - * Send a message to all logged in players - * @param {String} event Event name - * @param {String} data payload - */ -export function playersBroadcast(event, data) { - game.teams.forEach(team => teamBroadcast(team.id, event, data)); -} - -/** - * Send a socket message to all the players of a team - * @param {String} teamId The team that will receive the message - */ -export function sendUpdatedTeamInformations(teamId) { - // Test of parameters - if (!game.hasTeam(teamId)) return false; - // Variables - const team = game.getTeam(teamId); - const enemyTeam = game.getTeam(team.chasing); - teamBroadcast(teamId, "update_team", { - // Identification - name: team.name, - captureCode: team.captureCode, - // Chasing - captured: team.captured, - enemyName: enemyTeam?.name, - // Locations - lastSentLocation: team.lastSentLocation, - enemyLocation: team.enemyLocation, - // Placement phase - startingArea: team.startingArea, - ready: team.ready, - // Constraints - outOfZone: team.outOfZone, - outOfZoneDeadline: team.outOfZoneDeadline, - locationSendDeadline: team.locationSendDeadline, - hasHandicap: team.hasHandicap, - enemyHasHandicap: enemyTeam?.hasHandicap, - // Stats - distance: team.distance, - nCaptures: team.nCaptures, - nSentLocation: team.nSentLocation, - finishDate: team.finishDate, - }); -} - -export function initTeamSocket(io) { - io.of("player").on("connection", (socket) => { - console.log("Connection of a player"); - let teamId = null; - - const login = (loginTeamId) => { - logout(); - if (!game.addPlayer(loginTeamId, socket.id)) return false; - teamId = loginTeamId; - return true; - } - - const logout = () => { - if (!teamId) return; - game.removePlayer(teamId, socket.id); - teamId = null; - } - - socket.on("disconnect", () => { - console.log("Disconnection of a player"); - logout(); - }); - - socket.on("logout", () => { - logout(); - }); - - socket.on("login", (loginTeamId, callback) => { - if (!login(loginTeamId)) { - callback({ isLoggedIn: false, message: "Login denied" }); - return; - } - sendUpdatedTeamInformations(loginTeamId); - socket.emit("game_state", { - state: game.state, - date: game.startDate - }); - socket.emit("current_zone", { - begin: zoneManager.getCurrentZone(), - end: zoneManager.getNextZone(), - endDate: zoneManager.currentZone?.endDate, - }); - socket.emit("settings", game.getPlayerSettings()); - callback({ isLoggedIn : true, message: "Logged in"}); - }); - - socket.on("update_position", (position) => { - if (!teamId) return; - if (game.isPlayerCapitain(teamId, socket.id)) { - game.updateLocation(teamId, position); - } - }); - - socket.on("send_position", () => { - if (!teamId) return; - game.sendLocation(teamId); - }); - - socket.on("capture", (captureCode, callback) => { - if (!teamId) return; - if (game.tryCapture(teamId, captureCode)) { - callback({ hasCaptured : true, message: "Capture successful" }); - } else { - callback({ hasCaptured : false, message: "Capture denied" }); - } - }); - - socket.on("device_info", (infos) => { - if (!teamId) return; - if (game.isPlayerCapitain(teamId, socket.id)) { - game.updateTeam(teamId, {phoneModel: infos.model, phoneName: infos.name}); - } - }); - - socket.on("battery_update", (batteryLevel) => { - if (!teamId) return; - if (game.isPlayerCapitain(teamId, socket.id)) { - game.updateTeam(teamId, {battery: batteryLevel}); - } - }); - }); -} diff --git a/server/traque-back/src/old/timeout_handler.js b/server/traque-back/src/old/timeout_handler.js deleted file mode 100644 index 7b7101c..0000000 --- a/server/traque-back/src/old/timeout_handler.js +++ /dev/null @@ -1,92 +0,0 @@ -import game from "./game.js"; - -class TimeoutManager { - constructor() { - this.timeouts = new Map(); - } - - has(key) { - return this.timeouts.has(key); - } - - set(key, callback, delay) { - const newCallback = () => { - this.timeouts.delete(key); - callback(); - } - - if (this.timeouts.has(key)) clearTimeout(this.timeouts.get(key)); - this.timeouts.set(key, setTimeout(newCallback, delay)); - } - - clear(key) { - if (this.timeouts.has(key)) { - clearTimeout(this.timeouts.get(key)); - this.timeouts.delete(key); - } - } - - clearAll() { - this.timeouts.forEach(timeout => clearTimeout(timeout)); - this.timeouts = new Map(); - } -} - -export const sendPositionTimeouts = { - timeoutManager: new TimeoutManager(), - delay: 10, // Minutes - - has(teamID) { - return this.timeoutManager.has(teamID); - }, - - set(teamID) { - const callback = () => { - game.sendLocation(teamID); - this.set(teamID); - } - - this.timeoutManager.set(teamID, callback, this.delay * 60 * 1000); - }, - - clear(teamID) { - this.timeoutManager.clear(teamID); - }, - - clearAll() { - this.timeoutManager.clearAll(); - }, - - setDelay(delay) { - this.delay = delay; - } -} - -export const outOfZoneTimeouts = { - timeoutManager: new TimeoutManager(), - delay: 10, // Minutes - - has(teamID) { - return this.timeoutManager.has(teamID); - }, - - set(teamID) { - const callback = () => { - game.handicapTeam(teamID); - } - - this.timeoutManager.set(teamID, callback, this.delay * 60 * 1000); - }, - - clear(teamID) { - this.timeoutManager.clear(teamID); - }, - - clearAll() { - this.timeoutManager.clearAll(); - }, - - setDelay(delay) { - this.delay = delay; - } -} diff --git a/server/traque-back/src/old/trajectory.js b/server/traque-back/src/old/trajectory.js deleted file mode 100644 index 1f6db66..0000000 --- a/server/traque-back/src/old/trajectory.js +++ /dev/null @@ -1,90 +0,0 @@ -import fs from "fs"; -import path from "path"; -const TRAJECTORIES_DIR = path.join(process.cwd(), "trajectories"); -const EXTENSION = "txt"; - -// Useful functions - -function teamIDToPath(teamID) { - return path.join(TRAJECTORIES_DIR, teamID + "." + EXTENSION); -} - -function dataToLine(...data) { - return data.join(','); -} - -const errorFile = (err) => { - if (err) console.error("Error appending to file:", err); -}; - -function addLineToFile(teamID, line) { - // Insert the line in the file of teamID depending on the date (lines are sorted by date) - if (!fs.existsSync(teamIDToPath(teamID))) { - fs.writeFile(teamIDToPath(teamID), line + '\n', errorFile); - } else { - fs.readFile(teamIDToPath(teamID), 'utf8', (err, data) => { - if (err) { - errorFile(err); - return; - } - let lines = data.trim().split('\n'); - const newDate = parseInt(line.split(',')[0], 10); - let insertIndex = lines.length; - for (let i = lines.length - 1; i >= 0; i--) { - const date = parseInt(lines[i].split(',')[0], 10); - if (date <= newDate) { - insertIndex = i + 1; - break; - } - } - lines.splice(insertIndex, 0, line); - fs.writeFile(teamIDToPath(teamID), lines.join('\n') + '\n', errorFile); - }); - } -} - -function initTrajectories() { - const files = fs.readdirSync(TRAJECTORIES_DIR); - for (const file of files) fs.unlinkSync(path.join(TRAJECTORIES_DIR, file)); -} - -// Export functions - -export default { - isRecording: false, - - start() { - initTrajectories(); - this.isRecording = true; - }, - - stop() { - this.isRecording = false; - }, - - writePosition(date, teamID, lon, lat) { - if (this.isRecording) { - addLineToFile(teamID, dataToLine(date, "position", lon, lat)); - } - }, - - writeCapture(date, teamID, capturedTeamID) { - if (this.isRecording) { - addLineToFile(teamID, dataToLine(date, "capture", capturedTeamID)); - addLineToFile(capturedTeamID, dataToLine(date, "captured", teamID)); - } - }, - - writeSeePosition(date, teamID, seenTeamID) { - if (this.isRecording) { - addLineToFile(teamID, dataToLine(date, "see")); - addLineToFile(seenTeamID, dataToLine(date, "seen")); - } - }, - - writeOutOfZone(date, teamID, isOutOfZone) { - if (this.isRecording) { - addLineToFile(teamID, dataToLine(date, "zone", isOutOfZone)); - } - }, -} diff --git a/server/traque-back/src/services/photo.js b/server/traque-back/src/services/photo.js deleted file mode 100644 index 16beb73..0000000 --- a/server/traque-back/src/services/photo.js +++ /dev/null @@ -1,86 +0,0 @@ -import multer from "multer"; -import fs from "fs"; -import path from "path"; -import { gameManager } from "@/core/game_manager.js" - -const UPLOAD_DIR = path.join(process.cwd(), "uploads"); -const IMAGES_DIR = path.join(process.cwd(), "assets", "images"); -const ALLOWED_MIME = [ - "image/png", - "image/jpeg", - "image/gif" -] - -// Setup multer (the file upload middleware) -const storage = multer.diskStorage({ - // Save the file in the uploads directory - destination: function (req, file, callback) { - callback(null, UPLOAD_DIR); - }, - // Save the file with the team ID as the filename - filename: function (req, file, callback) { - const teamId = req.query.team; - if (typeof teamId === 'string') { - callback(null, teamId); - } - } -}); - -const upload = multer({ - storage, - // Only upload the file if it is a valid mime type and the team POST parameter is a valid team - fileFilter: function (req, file, callback) { - if (ALLOWED_MIME.indexOf(file.mimetype) == -1) { - callback(null, false); - } else if (!gameManager.teams.get(req.query.team)) { - callback(null, false); - } else { - callback(null, true); - } - } -}); - -const clean = () => { - const files = fs.readdirSync(UPLOAD_DIR); - for (const file of files) { - const filePath = path.join(UPLOAD_DIR, file); - fs.unlinkSync(filePath); - } -}; - -export const initPhotoUpload = (app) => { - clean(); - - // App handler for uploading a photo and saving it to a file - app.post("/upload", upload.single('file'), (req, res) => { - res.set("Access-Control-Allow-Origin", "*"); - console.log("upload", req.query); - res.send(""); - }); - - // App handler for serving the photo of a team given its secret ID - app.get("/photo/my", (req, res) => { - let team = gameManager.teams.get(req.query.team); - if (team) { - const imagePath = path.join(UPLOAD_DIR, team.id); - res.set("Content-Type", "image/png"); - res.set("Access-Control-Allow-Origin", "*"); - res.sendFile(fs.existsSync(imagePath) ? imagePath : path.join(IMAGES_DIR, "missing_image.jpg")); - } else { - res.status(400).send("Team not found"); - } - }); - - // App handler for serving the photo of the team chased by the team given by its secret ID - app.get("/photo/enemy", (req, res) => { - let team = gameManager.teams.get(req.query.team); - if (team) { - const imagePath = path.join(UPLOAD_DIR, team.chasing); - res.set("Content-Type", "image/png"); - res.set("Access-Control-Allow-Origin", "*"); - res.sendFile(fs.existsSync(imagePath) ? imagePath : path.join(IMAGES_DIR, "missing_image.jpg")); - } else { - res.status(400).send("Team not found"); - } - }); -}; diff --git a/server/traque-back/src/socket/adminHandler.js b/server/traque-back/src/socket/adminHandler.js deleted file mode 100644 index a461f63..0000000 --- a/server/traque-back/src/socket/adminHandler.js +++ /dev/null @@ -1,124 +0,0 @@ -import { createHash } from "crypto"; -import { gameManager } from "@/core/game_manager.js" -import { ADMIN_PASSWORD_HASH } from "@/util/util.js"; - -export const EVENTS = { - INTERNAL: { - TEAMS_UPDATE: "teams-update", - SETTINGS: "settings", - }, - IN: { - LOGIN: "login", - LOGOUT: "logout", - STATE: "state", - SETTINGS: "settings", - ADD_TEAM: "add-team", - REMOVE_TEAM: "remove-team", - REORDER_TEAM: "reorder-team", - ELIMINATE_TEAM: "eliminate-team", - REVIVE_TEAM: "revive-team", - }, - OUT: { - TEAMS_UPDATE: "teams-update", // TODO : Too big ? (Send only the changing teams ?) - SETTINGS: "settings", - }, -}; - -const ADMIN_ROOM = "admin_room"; - -export function initAdminSocketHandler(io) { - - // Util - - const emit = (targetId, event, data) => { - io.of("admin").to(targetId).emit(event, data); - }; - - const broadcast = (event, data) => { - io.of("admin").to(ADMIN_ROOM).emit(event, data); - }; - - - // Admin events - - io.of("admin").on("connection", (socket) => { - console.log("Connection of an admin"); - - // Variables - - let loggedIn = false; - - - // Util - - const logout = () => { - if (!loggedIn) return; - loggedIn = false; - socket.leave(ADMIN_ROOM); - } - - const login = (password) => { - if (loggedIn) return; - if (createHash('sha256').update(password).digest('hex') !== ADMIN_PASSWORD_HASH) return false; - loggedIn = true; - socket.join(ADMIN_ROOM); - } - - - // Socket - - socket.on("disconnect", () => { - console.log("Disconnection of an admin"); - logout(); - }); - - - // Authentication - - socket.on(EVENTS.IN.LOGIN, (password) => { - login(password); - }); - - socket.on(EVENTS.IN.LOGOUT, () => { - logout(); - }); - - - // Actions - - socket.on(EVENTS.IN.ADD_TEAM, (teamId) => { - if (!loggedIn) return; - gameManager.addTeam(teamId); - }); - - socket.on(EVENTS.IN.REMOVE_TEAM, (teamId) => { - if (!loggedIn) return; - gameManager.removeTeam(teamId); - }); - - socket.on(EVENTS.IN.REORDER_TEAM, (teamId) => { - if (!loggedIn) return; - gameManager.reorderTeam(teamId); - }); - - socket.on(EVENTS.IN.ELIMINATE_TEAM, (teamId) => { - if (!loggedIn) return; - gameManager.eliminate(teamId); - }); - - socket.on(EVENTS.IN.REVIVE_TEAM, (teamId) => { - if (!loggedIn) return; - gameManager.revive(teamId); - }); - - socket.on(EVENTS.IN.STATE, (state) => { - if (!loggedIn) return; - gameManager.setState(state); - }); - - socket.on(EVENTS.IN.SETTINGS, (settings) => { - if (!loggedIn) return; - gameManager.setSettings(settings); - }); - }); -} diff --git a/server/traque-back/src/socket/playerHandler.js b/server/traque-back/src/socket/playerHandler.js deleted file mode 100644 index 940b72e..0000000 --- a/server/traque-back/src/socket/playerHandler.js +++ /dev/null @@ -1,111 +0,0 @@ -import { gameManager } from "@/core/game_manager.js"; - -export const EVENTS = { - INTERNAL: { - LOGOUT: "logout", - TEAM_UPDATE: "team-update", - }, - IN: { - LOGIN: "login", - LOGOUT: "logout", - LOCATION: "location", - SCAN: "scan", - CAPTURE: "capture", - DEVICE: "device", - }, - OUT: { - LOGOUT: "logout", - TEAM_UPDATE: "team-update", - }, -}; - -export function initPlayerSocketHandler(io) { - - // Util - - const emit = (targetId, event, data) => { - io.of("player").to(targetId).emit(event, data); - }; - - - // Game manager events - - gameManager.on(EVENTS.INTERNAL.LOGOUT, (targetId) => { - emit(targetId, EVENTS.OUT.LOGOUT); - }); - - gameManager.on(EVENTS.INTERNAL.TEAM_UPDATE, (targetId, playTeamData) => { - emit(targetId, EVENTS.OUT.TEAM_UPDATE, playTeamData); - }); - - - // Player events - - io.of("player").on("connect", (socket) => { - console.log("Connection of a player"); - - // Variables - - let teamId = null; - - - // Util - - const isLoggedIn = () => { - return teamId !== null; - } - - const logout = () => { - if (!isLoggedIn()) return; - socket.leave(teamId); - teamId = null; - } - - const login = (loginTeamId) => { - if (!gameManager.teams.has(loginTeamId) || teamId === loginTeamId) return; - logout(); - teamId = loginTeamId - socket.join(teamId); - } - - - // Socket - - socket.on("disconnect", () => { - console.log("Disconnection of a player"); - logout(); - }); - - - // Authentication - - socket.on(EVENTS.IN.LOGIN, (loginTeamId, callback) => { - login(loginTeamId); - callback(isLoggedIn()); - if (isLoggedIn()) gameManager.emitTeamUpdate(socket.id, gameManager.teams.get(teamId)); - }); - - socket.on(EVENTS.IN.LOGOUT, () => { - logout(); - }); - - - // Actions - - socket.on(EVENTS.IN.LOCATION, (coords) => { - if (!isLoggedIn()) return; - gameManager.updateLocation(teamId, coords); - }); - - socket.on(EVENTS.IN.SCAN, (coords) => { - if (!isLoggedIn()) return; - gameManager.scan(teamId, coords); - }); - - socket.on(EVENTS.IN.CAPTURE, (captureCode, callback) => { - if (!isLoggedIn()) return; - const success = gameManager.capture(teamId, captureCode); - callback(success); - }); - }); -} diff --git a/server/traque-back/src/states/default_state.js b/server/traque-back/src/states/default_state.js deleted file mode 100644 index 061d952..0000000 --- a/server/traque-back/src/states/default_state.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GameState } from "@/states/game_state.js"; -import { DefaultTeamMapper } from "@/team/mapper/default_team_mapper.js"; - -export class DefaultState extends GameState { - constructor(manager) { - super(manager, new DefaultTeamMapper()); - } - - static get stateName () { - return "default"; - } - - // State functions - - updateLocation(team, coords) { - team.updateLocation(coords); - return true; - } -} diff --git a/server/traque-back/src/states/finished_state.js b/server/traque-back/src/states/finished_state.js deleted file mode 100644 index a2eb9f8..0000000 --- a/server/traque-back/src/states/finished_state.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GameState } from "@/states/game_state.js"; -import { FinishedTeamMapper } from "@/team/mapper/finished_team_mapper.js"; - -export class FinishedState extends GameState { - constructor(manager) { - super(manager, new FinishedTeamMapper()); - } - - static get stateName () { - return "finished"; - } - - // State functions - - updateLocation(team, coords) { - team.updateLocation(coords); - return true; - } -} diff --git a/server/traque-back/src/states/game_state.js b/server/traque-back/src/states/game_state.js deleted file mode 100644 index 7a64958..0000000 --- a/server/traque-back/src/states/game_state.js +++ /dev/null @@ -1,36 +0,0 @@ -export class GameState { - constructor(manager, teamMapper) { - this.manager = manager; - this.teamMapper = teamMapper; - } - - // Life cycle - - initTeamContext(_team) {} - - enter() { - this.manager.teams.forEach(team => this.initTeamContext(team)); - } - - clearTeamContext(_team) {} - - exit() { - this.manager.teams.forEach(team => this.clearTeamContext(team)); - } - - - // Hooks - - onTeamOrderChange() {} - - - // Mappers - - getTeamMapForTeam(team) { - return this.teamMapper.mapForTeam(team); - } - - getTeamMapForAdmin(team) { - return this.teamMapper.mapForAdmin(team); - } -} diff --git a/server/traque-back/src/states/placement_state.js b/server/traque-back/src/states/placement_state.js deleted file mode 100644 index 4c30fd6..0000000 --- a/server/traque-back/src/states/placement_state.js +++ /dev/null @@ -1,36 +0,0 @@ -import { haversineDistance } from "@/util/util.js"; -import { GameState } from "@/states/game_state.js"; -import { PlacementTeamMapper } from "@/team/mapper/placement_team_mapper.js"; - -export class PlacementState extends GameState { - constructor(manager) { - super(manager, new PlacementTeamMapper()); - } - - static get stateName () { - return "placement"; - } - - // Life cycle - - initTeamContext(team) { - team.context = { - placementZone: null, - isInPlacementZone: true, - }; - this.manager.emitTeamUpdate(team.id, team); - } - - - // State functions - - updateLocation(team, coords) { - team.updateLocation(coords); - - team.context.isInPlacementZone = ( - team.context.placementZone ? haversineDistance(team.location, team.context.placementZone.center) < team.context.placementZone.radius : true - ); - - return true; - } -} diff --git a/server/traque-back/src/states/playing_state.js b/server/traque-back/src/states/playing_state.js deleted file mode 100644 index dce7adf..0000000 --- a/server/traque-back/src/states/playing_state.js +++ /dev/null @@ -1,174 +0,0 @@ -import zoneManager from "@/core/zone_manager.js"; -import { randint } from "@/util/util.js"; -import { GameState } from "@/states/game_state.js"; -import { FinishedState } from "./finished_state.js"; -import { TimeoutManager } from "@/util/timeout_manager.js"; -import { PlayingTeamMapper } from "@/team/mapper/playing_team_mapper.js"; - - -const getNewCaptureCode = () => { - const codeLength = 4; - return randint(10 ** codeLength).toString().padStart(codeLength, '0'); -}; - - -export class PlayingState extends GameState { - constructor(manager) { - super(manager, new PlayingTeamMapper()); - } - - static get stateName () { - return "playing"; - } - - // Life cycle - - initTeamContext(team) { - team.context = { - // Team - scanLocation: team.location, - captureCode: getNewCaptureCode(), - // Booleans - isEliminated: false, - isOutOfZone: false, - hasHandicap: false, - // Timeouts - scanTimeout: new TimeoutManager(() => this.scan(team.id), this.manager.settings.scanDelay, true), - outOfZoneTimeout: new TimeoutManager(() => this.addHandicap(team.id), this.manager.settings.outOfZoneDelay, true), - // Hunter and target - hunter: null, - target: null, - targetScanLocation: { coords: null, timesptamp: null }, - }; - this.manager.emitTeamUpdate(team.id, team); - } - - enter() { - super.enter(); - this.onTeamOrderChange(); - zoneManager.start(); - } - - clearTeamContext(team) { - team.context.scanTimeout.clear(); - team.context.outOfZoneTimeout.clear(); - } - - exit() { - super.exit(); - zoneManager.stop(); - } - - - // Hooks - - onTeamOrderChange() { - const playingTeamsOrder = this.manager.teams.order.filter(team => !team.context.isEliminated); - const length = playingTeamsOrder.length; - playingTeamsOrder.forEach((team, i) => { - const hunter = this.manager.teams.get(playingTeamsOrder[(i+length-1) % length]); - const target = this.manager.teams.get(playingTeamsOrder[(i+1) % length]); - let hasChanged = false; - if (!team.context.hunter.equals(hunter)) { - team.context.hunter = hunter; - hasChanged = true; - } - if (!team.context.target.equals(target)) { - team.context.target = target; - team.context.targetScanLocation = target.context.location; - hasChanged = true; - } - if (hasChanged) { - this.manager.emitTeamUpdate(team.id, team); - } - }); - - if (this.manager.teams.order.filter(team => !team.context.isEliminated).length <= 2) { - this.manager.setState(FinishedState); - } - - return true; - } - - - // State functions - - eliminate(team) { - if (team.context.isEliminated) return false; - - this.clearTeamContext(team); - team.context.isEliminated = true; - this.onTeamOrderChange(); - - return true; - } - - revive(team) { - if (!team.context.isEliminated) return false; - - this.initTeamContext(team); - this.onTeamOrderChange(); - - return true; - } - - addHandicap(team) { - if (team.context.hasHandicap) return false; - - team.context.hasHandicap = true; - team.context.scanTimeout.clear(); - this.manager.emitTeamUpdate(team.id, team); - - return true; - } - - clearHandicap(team) { - if (!team.context.hasHandicap) return false; - - team.context.hasHandicap = false; - team.context.scanTimeout.set(); - this.manager.emitTeamUpdate(team.id, team); - - return true; - } - - scan(team) { - if (team.context.hasHandicap || team.context.isEliminated) return false; - - team.context.scanLocation = team.location; - team.context.targetScanLocation = team.context.target.context.scanLocation; - team.context.scanTimeout.set(); - this.manager.emitTeamUpdate(team.id, team); - - return true; - } - - capture(team, captureCode) { - if (team.context.hasHandicap || team.context.isEliminated) return false; - - if (captureCode != team.context.target.context.captureCode) return false; - this.eliminate(team.context.target); - - return true; - } - - updateLocation(team, coords) { - team.updateLocation(coords); - - const isOutOfZone = !zoneManager.isInZone(team.location); - // Exit zone case - if (isOutOfZone && !team.context.isOutOfZone) { - team.context.isOutOfZone = true; - team.context.outOfZoneTimeout.set(); - this.manager.emitTeamUpdate(team.id, team); - // Enter zone case - } else if (!isOutOfZone && team.context.isOutOfZone) { - team.context.isOutOfZone = false; - team.context.outOfZoneTimeout.clear(); - this.clearHandicap(team); - this.manager.emitTeamUpdate(team.id, team); - } - - return true; - } -} diff --git a/server/traque-back/src/team/mapper/default_team_mapper.js b/server/traque-back/src/team/mapper/default_team_mapper.js deleted file mode 100644 index 615d10b..0000000 --- a/server/traque-back/src/team/mapper/default_team_mapper.js +++ /dev/null @@ -1,20 +0,0 @@ -import { TeamMapper } from "./team_mapper.js"; -import { DefaultState } from "@/states/default_state.js"; - -export class DefaultTeamMapper extends TeamMapper { - mapForTeam(team) { - return { - ...super.mapForTeam(team), - state: DefaultState.stateName, - context: {} - }; - } - - mapForAdmin(team) { - return { - ...super.mapForAdmin(team), - state: DefaultState.stateName, - context: {} - }; - } -} diff --git a/server/traque-back/src/team/mapper/finished_team_mapper.js b/server/traque-back/src/team/mapper/finished_team_mapper.js deleted file mode 100644 index 00223a0..0000000 --- a/server/traque-back/src/team/mapper/finished_team_mapper.js +++ /dev/null @@ -1,20 +0,0 @@ -import { TeamMapper } from "./team_mapper.js"; -import { FinishedState } from "@/states/finished_state.js"; - -export class FinishedTeamMapper extends TeamMapper { - mapForTeam(team) { - return { - ...super.mapForTeam(team), - state: FinishedState.stateName, - context: {} - }; - } - - mapForAdmin(team) { - return { - ...super.mapForAdmin(team), - state: FinishedState.stateName, - context: {} - }; - } -} diff --git a/server/traque-back/src/team/mapper/placement_team_mapper.js b/server/traque-back/src/team/mapper/placement_team_mapper.js deleted file mode 100644 index d50aeef..0000000 --- a/server/traque-back/src/team/mapper/placement_team_mapper.js +++ /dev/null @@ -1,26 +0,0 @@ -import { TeamMapper } from "./team_mapper.js"; -import { PlacementState } from "@/states/placement_state.js"; - -export class PlacementTeamMapper extends TeamMapper { - mapForTeam(team) { - return { - ...super.mapForTeam(team), - state: PlacementState.stateName, - context: { - placementZone: team.context.placementZone, - isInPlacementZone: team.context.isInPlacementZone, - } - }; - } - - mapForAdmin(team) { - return { - ...super.mapForAdmin(team), - state: PlacementState.stateName, - context: { - placementZone: team.context.placementZone, - isInPlacementZone: team.context.isInPlacementZone, - } - }; - } -} diff --git a/server/traque-back/src/team/mapper/playing_team_mapper.js b/server/traque-back/src/team/mapper/playing_team_mapper.js deleted file mode 100644 index 39ed774..0000000 --- a/server/traque-back/src/team/mapper/playing_team_mapper.js +++ /dev/null @@ -1,56 +0,0 @@ -import { TeamMapper } from "./team_mapper.js"; -import { PlayingState } from "@/states/playing_state.js"; -import zoneManager from "@/core/zone_manager.js"; - -export class PlayingTeamMapper extends TeamMapper { - mapForTeam(team) { - return { - ...super.mapForTeam(team), - state: PlayingState.stateName, - context: { - // Team - captureCode: team.context.captureCode, - scanLocation: team.context.scanLocation, - // Booleans - isEliminated: team.context.isEliminated, - isOutOfZone: team.context.isOutOfZone, - hasHandicap: team.context.hasHandicap, - // Timeouts - scanRemainingTime: team.context.scanTimeout.remainingTime, - outOfZoneRemainingTime: team.context.outOfZoneTimeout.remainingTime, - // Target - targetName: team.context.target?.name, - targetScanLocation: team.context.targetScanLocation, - targetHasHandicap: team.context.hunter?.context.hasHandicap, - // Zone - zoneType: zoneManager.settings.zoneType, - zoneCurrent: zoneManager.getCurrentZone(), - zoneNext: zoneManager.getNextZone(), - zoneRemainingTime: zoneManager.currentZone?.endDate ? Math.max(0, zoneManager.currentZone.endDate - Date.now()) : null - } - }; - } - - mapForAdmin(team) { - return { - ...super.mapForAdmin(team), - state: PlayingState.stateName, - context: { - // Team - captureCode: team.context.captureCode, - scanLocation: team.context.scanLocation, - // Booleans - isEliminated: team.context.isEliminated, - isOutOfZone: team.context.isOutOfZone, - hasHandicap: team.context.hasHandicap, - // Timeouts - scanRemainingTime: team.context.scanTimeout.remainingTime, - outOfZoneRemainingTime: team.context.outOfZoneTimeout.remainingTime, - // Target and hunter - hunterName: team.context.hunter.name, - targetName: team.context.target.name, - targetScanLocation: team.context.targetScanLocation, - } - }; - } -} diff --git a/server/traque-back/src/team/mapper/team_mapper.js b/server/traque-back/src/team/mapper/team_mapper.js deleted file mode 100644 index 4a3a3c0..0000000 --- a/server/traque-back/src/team/mapper/team_mapper.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GameState } from "@/states/game_state.js"; - -export class TeamMapper { - mapForTeam(team) { - return { - id: team.id, - name: team.name, - state: GameState.stateName, - context: {} - }; - } - - mapForAdmin(team) { - return { - id: team.id, - name: team.name, - location: team.location, - connectedPlayerCount: team.connectedPlayerCount, - state: GameState.stateName, - context: {} - }; - } -} diff --git a/server/traque-back/src/team/team.js b/server/traque-back/src/team/team.js deleted file mode 100644 index f07e1d2..0000000 --- a/server/traque-back/src/team/team.js +++ /dev/null @@ -1,19 +0,0 @@ -export class Team { - constructor(id, teamName) { - // Identity - this.id = id; - this.name = teamName; - // Location - this.location = { coords: null, timestamp: null }; - // Context - this.context = {}; - } - - updateLocation(coords) { - this.location = { coords: coords, timestamp: Date.now()} - } - - equals(team) { - return this.id === team.id; - } -} diff --git a/server/traque-back/src/util/circular_map.js b/server/traque-back/src/util/circular_map.js deleted file mode 100644 index c4249ca..0000000 --- a/server/traque-back/src/util/circular_map.js +++ /dev/null @@ -1,33 +0,0 @@ -export class CircularMap extends Map { - constructor(entries) { - super(entries); - this.order = Array.from(this.keys()); - } - - set(key, value) { - if (!super.has(key)) { - this.order.push(key); - } - return super.set(key, value); - } - - delete(key) { - if (super.delete(key)) { - this.order = this.order.filter(k => k !== key); - return true; - } - return false; - } - - clear() { - this.order = []; - super.clear(); - } - - reorder(newOrder) { - const isValid = newOrder.length === this.size && new Set([...this.order, ...newOrder]).size === this.size; - if (!isValid) return false; - this.order = newOrder; - return true; - } -} diff --git a/server/traque-back/src/util/random.js b/server/traque-back/src/util/random.js new file mode 100644 index 0000000..f52a21e --- /dev/null +++ b/server/traque-back/src/util/random.js @@ -0,0 +1 @@ +export const randint = (max) => Math.floor(Math.random() * max); diff --git a/server/traque-back/src/util/scheduler.js b/server/traque-back/src/util/scheduler.js new file mode 100644 index 0000000..be307c8 --- /dev/null +++ b/server/traque-back/src/util/scheduler.js @@ -0,0 +1,56 @@ +export class Scheduler { + constructor() { + this._id = null; + this._date = null; + } + + get isActive() { + return this._id !== null; + } + + get dateOfExecution() { + return this._date; + } + + get timeToExecution() { + return this.isActive ? Math.max(0, this._date - Date.now()) : null; + } + + start(callback, delay) { + this.interrupt(); + this._id = setTimeout(() => { this._clean(); callback(); }, delay); + this._date = Date.now() + delay; + return this; + } + + interrupt() { + if (!this.isActive) return; + clearTimeout(this._id); + this._clean(); + } + + _clean() { + this._id = null; + this._date = null; + } +} + +export class ScheduledTask extends Scheduler { + constructor(callback, delay) { + super(); + this._callback = callback; + this._delay = delay; + } + + start() { + return super.start(this._callback, this._delay); + } + + setDelay(delay, restart = false) { + this._delay = delay; + if (restart && this.isActive) { + this.interrupt(); + this.start(); + } + } +} diff --git a/server/traque-back/src/util/state_tracker.js b/server/traque-back/src/util/state_tracker.js new file mode 100644 index 0000000..fc710ac --- /dev/null +++ b/server/traque-back/src/util/state_tracker.js @@ -0,0 +1,14 @@ +export class StateTracker { + constructor(mapper) { + this._mapper = mapper; + this._hash = null; + } + + getSyncDto() { + const dto = this._mapper.map(); + const currentHash = this._mapper.hash(dto); + const hasChanged = currentHash !== this._hash; + this._hash = currentHash; + return { dto, hasChanged }; + } +} diff --git a/server/traque-back/src/util/timeout_manager.js b/server/traque-back/src/util/timeout_manager.js deleted file mode 100644 index 0d99756..0000000 --- a/server/traque-back/src/util/timeout_manager.js +++ /dev/null @@ -1,26 +0,0 @@ -export class TimeoutManager { - constructor(callback, delay, set = false) { - this.callback = callback; - this.delay = delay; - this.id = null; - this.date = null; - if (set) this.set(); - } - - get remainingTime() { - if (!this.id) return null; - return Math.max(0, this.date - Date.now()); - } - - set() { - if (this.id) clearTimeout(this.id); - this.id = setTimeout(this.callback, this.delay); - this.date = Date.now() + this.delay; - } - - clear() { - if (this.id) clearTimeout(this.id); - this.id = null; - this.date = null; - } -} diff --git a/server/traque-back/src/util/util.js b/server/traque-back/src/util/util.js deleted file mode 100644 index 5aa97c5..0000000 --- a/server/traque-back/src/util/util.js +++ /dev/null @@ -1,22 +0,0 @@ -import { config } from "dotenv"; - -config(); - -export const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH || ""; -export const HOST = process.env.HOST || "0.0.0.0"; -export const PORT = Number(process.env.PORT) || 3000; - -export const EARTH_RADIUS = 6_371_000; // Radius of the earth in meters - -export const randint = (max) => { - return Math.floor(Math.random() * max); -} - -export const haversineDistance = ({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) => { - // Return the distance in meters between the two points of coordinates - const degToRad = (deg) => deg * (Math.PI / 180); - const dLat = degToRad(lat2 - lat1); - const dLon = degToRad(lon2 - lon1); - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - return 2 * EARTH_RADIUS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); -}