Added modified version after migrating

This commit is contained in:
2025-06-25 13:40:17 +03:00
parent 3d5614ac5b
commit 390c637cec
6 changed files with 351 additions and 52 deletions

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

4
captain-definition Normal file
View File

@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}

236
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.9.0",
"express": "^4.21.0", "express": "^4.21.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"moment": "^2.30.1" "moment": "^2.30.1"
@ -33,6 +34,23 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.3", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@ -85,6 +103,31 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"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/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -107,9 +150,9 @@
} }
}, },
"node_modules/cookie": { "node_modules/cookie": {
"version": "0.6.0", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@ -147,6 +190,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -166,6 +218,20 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"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/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -182,13 +248,10 @@
} }
}, },
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT", "license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -202,6 +265,33 @@
"node": ">= 0.4" "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/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -218,9 +308,9 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
@ -228,7 +318,7 @@
"body-parser": "1.20.3", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.7.1",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -242,7 +332,7 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.10", "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.13.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
@ -257,6 +347,10 @@
}, },
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
@ -277,6 +371,42 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -305,16 +435,21 @@
} }
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.4", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "get-proto": "^1.0.1",
"has-symbols": "^1.0.3", "gopd": "^1.2.0",
"hasown": "^2.0.0" "has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -323,13 +458,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gopd": { "node_modules/get-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"get-intrinsic": "^1.1.3" "dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -347,10 +495,10 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-proto": { "node_modules/has-symbols": {
"version": "1.0.3", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -359,11 +507,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-symbols": { "node_modules/has-tostringtag": {
"version": "1.0.3", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT", "license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -432,6 +583,15 @@
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"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": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -550,9 +710,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.10", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
@ -568,6 +728,12 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.13.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",

View File

@ -9,6 +9,7 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"axios": "^1.9.0",
"express": "^4.21.0", "express": "^4.21.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"moment": "^2.30.1" "moment": "^2.30.1"

View File

@ -9,6 +9,8 @@
#controlPanel { #controlPanel {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px;
flex-wrap: wrap;
} }
#fileInfo { #fileInfo {
margin-left: 10px; margin-left: 10px;
@ -17,36 +19,58 @@
</head> </head>
<body> <body>
<a href="https://github.com/eetnaviation/tlt-gps-replay"><h1>TLT-GPS-REPLAY</h1></a> <a href="https://github.com/eetnaviation/tlt-gps-replay"><h1>TLT-GPS-REPLAY</h1></a>
<p>If you want to supply your own files, please download the app from github and run it yourself. Link in title.</p>
<p>This site only stores the last 7 days. If there are less files, it probably hasn't ran for 7 days yet to fill up the slots.</p>
<p id="intervalMessage">Playback speed: PAUSED</p> <p id="intervalMessage">Playback speed: PAUSED</p>
<div id="map"></div> <div id="map"></div>
<div id="controlPanel"> <div id="controlPanel">
<button id="playButton">Play</button> <button id="playButton">Play</button>
<label for="speedSlider">Speed:</label>
<p><-- FASTER</p>
<input type="range" id="speedSlider" min="10" max="31" value="31">
<span id="speedDisplay">31ms</span>
<p>--> SLOWER</p>
<div id="fileInfo"> <div id="fileInfo">
<span id="fileName">No file</span> - <span id="fileTimestamp">N/A</span> <span id="fileName">No file</span> - <span id="fileTimestamp">N/A</span>
</div> </div>
</div> </div>
<p id="statusMessage">No files loaded</p> <!-- Status message paragraph -->
<div id="datetimeControl" style="margin-top: 10px;">
<label for="datetimeSlider">Select Date/Time:</label><br>
<input type="range" id="datetimeSlider" min="0" max="0" value="0" style="width: 100%;">
<div><span id="datetimeDisplay">N/A</span></div>
</div>
<p id="statusMessage">No files loaded</p>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script> <script>
const map = L.map('map').setView([59.43041, 24.75924], 13); const map = L.map('map').setView([59.43041, 24.75924], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors' attribution: '© OpenStreetMap contributors | TLT-GPS-REPLAY made by eetnaviation (VELEND.EU)'
}).addTo(map); }).addTo(map);
let gpsData = []; let gpsData = [];
let markers = {}; let markers = {};
let currentFileIndex = 0; let currentFileIndex = 0;
let intervalId; let intervalId;
let intervalInt = 31;
const datetimeSlider = document.getElementById('datetimeSlider');
const datetimeDisplay = document.getElementById('datetimeDisplay');
fetch('/gps-files') fetch('/gps-files')
.then(response => response.json()) .then(response => response.json())
.then(files => { .then(files => {
gpsData = files; gpsData = files;
updateProgress(); // Initial update for 0/total
if (gpsData.length > 0) { if (gpsData.length > 0) {
loadGPSData(0); // Load the first file datetimeSlider.max = gpsData.length - 1;
datetimeSlider.value = 0;
datetimeDisplay.textContent = gpsData[0].timestamp;
loadGPSData(0);
} }
updateProgress();
}); });
function loadGPSData(index) { function loadGPSData(index) {
@ -59,7 +83,7 @@
fetch(`/gps/${file.name}`) fetch(`/gps/${file.name}`)
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
const lines = data.split('\n').slice(1); // Skip header const lines = data.split('\n').slice(1);
lines.forEach(line => { lines.forEach(line => {
const [type, lineNum, lon, lat, , , tak, , , destination] = line.split(','); const [type, lineNum, lon, lat, , , tak, , , destination] = line.split(',');
if (lon && lat) { if (lon && lat) {
@ -73,44 +97,70 @@
} }
} }
}); });
// Update progress message
updateProgress(); updateProgress();
}); });
} }
function updateProgress() { function updateProgress() {
const statusMessage = document.getElementById('statusMessage'); document.getElementById('statusMessage').textContent = `${currentFileIndex + 1}/${gpsData.length} files done`;
statusMessage.textContent = `${currentFileIndex + 1}/${gpsData.length} files done`;
} }
document.getElementById('playButton').addEventListener('click', () => { document.getElementById('playButton').addEventListener('click', () => {
const playButton = document.getElementById('playButton'); const playButton = document.getElementById('playButton');
const statusMessage = document.getElementById('statusMessage'); const statusMessage = document.getElementById('statusMessage');
intervalInt = 31;
if (intervalId) { if (intervalId) {
clearInterval(intervalId); clearInterval(intervalId);
intervalId = null; intervalId = null;
playButton.textContent = 'Play'; playButton.textContent = 'Play';
statusMessage.textContent = ''; // Clear status message statusMessage.textContent = '';
intervalMessage.textContent = 'Playback speed: PAUSED'; intervalMessage.textContent = 'Playback speed: PAUSED';
} else { } else {
intervalMessage.textContent = 'Playback speed: ' + intervalInt + 'ms'; intervalMessage.textContent = 'Playback speed: ' + intervalInt + 'ms';
intervalId = setInterval(() => { intervalId = setInterval(() => {
loadGPSData(currentFileIndex); loadGPSData(currentFileIndex);
datetimeSlider.value = currentFileIndex;
datetimeDisplay.textContent = gpsData[currentFileIndex]?.timestamp || 'N/A';
currentFileIndex++; currentFileIndex++;
if (currentFileIndex >= gpsData.length) { if (currentFileIndex >= gpsData.length) {
clearInterval(intervalId); clearInterval(intervalId);
intervalId = null; intervalId = null;
playButton.textContent = 'Play'; playButton.textContent = 'Play';
statusMessage.textContent = 'Files ended'; // Show final message statusMessage.textContent = 'Files ended';
} }
}, intervalInt); // Adjust interval as needed }, intervalInt);
playButton.textContent = 'Pause'; playButton.textContent = 'Pause';
} }
}); });
const speedSlider = document.getElementById('speedSlider');
const speedDisplay = document.getElementById('speedDisplay');
speedSlider.addEventListener('input', () => {
intervalInt = parseInt(speedSlider.value);
speedDisplay.textContent = intervalInt + 'ms';
if (intervalId) {
clearInterval(intervalId);
intervalId = setInterval(() => {
loadGPSData(currentFileIndex);
datetimeSlider.value = currentFileIndex;
datetimeDisplay.textContent = gpsData[currentFileIndex]?.timestamp || 'N/A';
currentFileIndex++;
if (currentFileIndex >= gpsData.length) {
clearInterval(intervalId);
intervalId = null;
document.getElementById('playButton').textContent = 'Play';
document.getElementById('statusMessage').textContent = 'Files ended';
}
}, intervalInt);
intervalMessage.textContent = 'Playback speed: ' + intervalInt + 'ms';
}
});
datetimeSlider.addEventListener('input', () => {
const index = parseInt(datetimeSlider.value);
currentFileIndex = index;
datetimeDisplay.textContent = gpsData[index]?.timestamp || 'N/A';
loadGPSData(index);
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,15 +1,22 @@
const express = require('express'); const express = require('express');
const axios = require('axios');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const app = express(); const app = express();
const port = 80; const port = 80;
const url = 'http://transport.tallinn.ee/gps.txt';
const fetchInterval = 10 * 1000; // 10 seconds
const retentionPeriod = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
const gpsFilePath = 'gps/';
const logFilePath = 'logs/';
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
app.use('/gps', express.static(path.join(__dirname, 'gps'))); // Serve static files from the gps directory app.use('/gps', express.static(path.join(__dirname, 'gps'))); // Serve static files from the gps directory
app.get('/gps-files', (req, res) => { app.get('/gps-files', (req, res) => {
const gpsDir = path.join(__dirname, 'gps'); const gpsFilePath = path.join(__dirname, 'gps');
fs.readdir(gpsDir, (err, files) => { fs.readdir(gpsFilePath, (err, files) => {
if (err) return res.status(500).send(err.message); if (err) return res.status(500).send(err.message);
const gpsFiles = files const gpsFiles = files
.filter(file => file.startsWith('gps-') && file.endsWith('.txt')) .filter(file => file.startsWith('gps-') && file.endsWith('.txt'))
@ -24,3 +31,65 @@ app.get('/gps-files', (req, res) => {
app.listen(port, () => { app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`); console.log(`Server running at http://localhost:${port}`);
}); });
if (!fs.existsSync(gpsFilePath)) fs.mkdirSync(gpsFilePath);
function writeToLog(filename, data) {
fs.appendFileSync(filename, data);
}
function deleteOldFiles() {
const now = Date.now();
fs.readdirSync(gpsFilePath).forEach(file => {
const filePath = path.join(gpsFilePath, file);
const stats = fs.statSync(filePath);
if (now - stats.mtimeMs > retentionPeriod) {
fs.unlinkSync(filePath);
}
});
}
async function fetchAndSaveData() {
try {
console.log(`Fetch started`);
const date = new Date();
const timestamp = `gps-${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}-${String(date.getHours()).padStart(2,'0')}_${String(date.getMinutes()).padStart(2,'0')}_${String(date.getSeconds()).padStart(2,'0')}.txt`;
const filePath = path.join(gpsFilePath, timestamp);
const logData = `${date} - Fetching new data...\n`;
writeToLog('fetch_log.txt', logData);
const response = await axios.get(url);
if (response.status === 200) {
fs.writeFileSync(filePath, response.data, 'utf8');
console.log("Fetch completed.");
writeToLog('fetch_log.txt', `${date} - Fetching completed!\n`);
} else {
writeToLog('errors_log.txt', `${date} - Fetching failed. Got: ${response.status}\n`);
}
deleteOldFiles();
} catch (error) {
const date = new Date();
writeToLog('errors_log.txt', `${date} - Error fetching data: ${error.message}\n`);
}
}
function writeToLog(logfile, logdata) {
const realLogFile = path.join(logFilePath, logfile);
const dir = path.dirname(realLogFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(realLogFile)) {
fs.writeFileSync(realLogFile, '', 'utf8');
}
fs.appendFile(realLogFile, logdata, 'utf8', (err) => {
if (err) {
console.error('Error writing to file:', err);
}
});
}
setInterval(fetchAndSaveData, fetchInterval);
fetchAndSaveData();