diff --git a/.gitignore b/.gitignore index c331ffb4..8ab69d68 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ node_modules !client/bin /client/public/plugins /client/plugins/controltips/index.js +*.hgt +/scripts/python/hgt diff --git a/client/app.js b/client/app.js index 1b2edaa9..65dfd408 100644 --- a/client/app.js +++ b/client/app.js @@ -6,6 +6,7 @@ var fs = require('fs'); var atcRouter = require('./routes/api/atc'); var airbasesRouter = require('./routes/api/airbases'); +var elevationRouter = require('./routes/api/elevation'); var indexRouter = require('./routes/index'); var uikitRouter = require('./routes/uikit'); var usersRouter = require('./routes/users'); @@ -23,6 +24,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/api/atc', atcRouter); app.use('/api/airbases', airbasesRouter); +app.use('/api/elevation', elevationRouter); app.use('/plugins', pluginsRouter) app.use('/users', usersRouter); app.use('/uikit', uikitRouter); diff --git a/client/package-lock.json b/client/package-lock.json index 14431e48..fcf08db4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2761,6 +2761,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -3294,6 +3299,11 @@ "ieee754": "^1.1.4" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3936,6 +3946,11 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, + "events-intercept": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/events-intercept/-/events-intercept-2.0.0.tgz", + "integrity": "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==" + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -3998,12 +4013,25 @@ "basic-auth": "^2.0.1" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "requires": { + "pend": "~1.2.0" + } + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -4840,6 +4868,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -5068,6 +5104,11 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5129,6 +5170,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "requires": { + "asap": "~2.0.6" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5579,6 +5628,34 @@ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true }, + "srtm-elevation": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/srtm-elevation/-/srtm-elevation-2.1.2.tgz", + "integrity": "sha512-VAYD86JwB4l4yXNJmlTVy5ryQBu0bq50rOvPqpNu4pbKwgPrc7QijjYFKKcM+AfnDvbmlaZv+qWWMGYg+mvBVg==", + "requires": { + "extend": "^3.0.2", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.1", + "promise": "^8.1.0", + "yauzl": "^2.10.0", + "yauzl-promise": "^2.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -5829,6 +5906,11 @@ "nopt": "~1.0.10" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -6114,6 +6196,20 @@ } } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", @@ -6183,6 +6279,32 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yauzl-clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/yauzl-clone/-/yauzl-clone-1.0.4.tgz", + "integrity": "sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==", + "requires": { + "events-intercept": "^2.0.0" + } + }, + "yauzl-promise": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yauzl-promise/-/yauzl-promise-2.1.3.tgz", + "integrity": "sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==", + "requires": { + "yauzl": "^2.9.1", + "yauzl-clone": "^1.0.4" + } } } } diff --git a/client/package.json b/client/package.json index d616d0f8..69c7c9fe 100644 --- a/client/package.json +++ b/client/package.json @@ -16,7 +16,8 @@ "express": "~4.16.1", "express-basic-auth": "^1.2.1", "morgan": "~1.9.1", - "save": "^2.9.0" + "save": "^2.9.0", + "srtm-elevation": "^2.1.2" }, "devDependencies": { "@babel/preset-env": "^7.21.4", diff --git a/client/public/stylesheets/panels/mouseinfo.css b/client/public/stylesheets/panels/mouseinfo.css index df33c28b..905c26ba 100644 --- a/client/public/stylesheets/panels/mouseinfo.css +++ b/client/public/stylesheets/panels/mouseinfo.css @@ -106,4 +106,14 @@ text-overflow: ellipsis; white-space: nowrap; color: var(--background-offwhite); +} + +.elevation::after { + content: attr(data-value); + font-weight: bold; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--background-offwhite); } \ No newline at end of file diff --git a/client/routes/api/elevation.js b/client/routes/api/elevation.js new file mode 100644 index 00000000..33c2e926 --- /dev/null +++ b/client/routes/api/elevation.js @@ -0,0 +1,28 @@ +const express = require('express'); +var fs = require('fs'); +const router = express.Router(); +const TileSet = require('srtm-elevation').TileSet; +const SRTMElevationDownloader = require('srtm-elevation').SRTMElevationDownloader; + +let rawdata = fs.readFileSync('../olympus.json'); +let config = JSON.parse(rawdata); +var tileset = null; + +if (config["client"] === undefined || config["client"]["elevationProvider"] === undefined) + tileset = new TileSet('./hgt'); +else + tileset = new TileSet('./hgt', {downloader: new SRTMElevationDownloader('./hgt', config["client"]["elevationProvider"])}); + +router.get( "/:lat/:lng", ( req, res ) => { + tileset.getElevation([req.params.lat, req.params.lng], function(err, elevation) { + if (err) { + console.log('getElevation failed: ' + err.message); + res.send("n/a"); + } else { + res.send(String(elevation)); + } + }); + +}); + +module.exports = router; \ No newline at end of file diff --git a/client/routes/plugins.js b/client/routes/plugins.js index 1ed9511f..fd3ad0af 100644 --- a/client/routes/plugins.js +++ b/client/routes/plugins.js @@ -16,7 +16,6 @@ function listDirectories(source) { router.get('/list', function (req, res) { var directories = listDirectories(pluginsDirectory); - console.log(directories) res.send(directories.filter(directory => fs.existsSync(path.join(pluginsDirectory, directory)))); }); diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 608dcd73..da3fa32c 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -101,7 +101,7 @@ export const mapBounds = { // TODO "Falklands" } -export const layers = { +export const mapLayers = { "ArcGIS Satellite": { urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", maxZoom: 20, diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 479bae5e..da0da642 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants"; +import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants"; import { TargetMarker } from "./markers/targetmarker"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index 90ba077c..bec57566 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -1,6 +1,6 @@ import { Icon, LatLng, Marker, Polyline } from "leaflet"; import { getApp } from ".."; -import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils"; +import { distance, bearing, zeroAppend, mToNm, nmToFt, mToFt } from "../other/utils"; import { Unit } from "../unit/unit"; import { Panel } from "./panel"; import formatcoords from "formatcoords"; @@ -11,6 +11,7 @@ export class MouseInfoPanel extends Panel { #measureIcon: Icon; #measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false }); #measureBox: HTMLElement; + #elevationRequest: XMLHttpRequest | null = null; constructor(ID: string) { super( ID ); @@ -53,6 +54,30 @@ export class MouseInfoPanel extends Panel { var coordString = coords.format('XDDMMss', {decimalPlaces: 4}); this.#drawCoordinates("ref-mouse-position-latitude", "mouse-position-latitude", coordString.split(" ")[0]); this.#drawCoordinates("ref-mouse-position-longitude", "mouse-position-longitude", coordString.split(" ")[1]); + + /* Get the ground elevation from the server endpoint */ + if (this.#elevationRequest == null) { + this.#elevationRequest = new XMLHttpRequest(); + this.#elevationRequest.open('GET', `api/elevation/${mousePosition.lat}/${mousePosition.lng}`, true); + this.#elevationRequest.timeout = 500; // ms + this.#elevationRequest.responseType = 'json'; + this.#elevationRequest.onload = () => { + var status = this.#elevationRequest?.status; + if (status === 200) { + const el = this.getElement().querySelector(`#mouse-position-elevation`) as HTMLElement; + try { + el.dataset.value = `${Math.floor(mToFt(parseFloat(this.#elevationRequest?.response)))} ft`; + } catch { + el.dataset.value = `N/A`; + } + } + this.#elevationRequest = null; + }; + this.#elevationRequest.ontimeout = () => {this.#elevationRequest = null;} + this.#elevationRequest.onerror = () => {this.#elevationRequest = null;} + this.#elevationRequest.onabort = () => {this.#elevationRequest = null;} + this.#elevationRequest.send(); + } } #onMapClick(e: any) { @@ -115,7 +140,6 @@ export class MouseInfoPanel extends Panel { } #onMouseMove(e: any) { - this.#update(); this.#drawMeasureLine(); } diff --git a/client/views/panels/mouseinfo.ejs b/client/views/panels/mouseinfo.ejs index c4434cc6..79557502 100644 --- a/client/views/panels/mouseinfo.ejs +++ b/client/views/panels/mouseinfo.ejs @@ -25,9 +25,11 @@