├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── doc └── NetArchitecture.odp ├── favicon.ico ├── geoserver └── cesiumserver.js ├── package-lock.json ├── package.json ├── public ├── css │ └── plimap.css ├── favicon.ico ├── img │ ├── 10130100000000000000.png │ ├── 10130100001102000802.png │ ├── 10130100001103000000.png │ ├── 10131000000000000000.png │ ├── 10131000001900000000.png │ ├── 10131000101212000000.png │ ├── 10131000111211000000.png │ ├── 10131000121211000000.png │ ├── 10131000121211040000.png │ ├── 10131000141211000000.png │ ├── 10131000141211950000.png │ ├── 10131000151211950000.png │ ├── 10131000161211950000.png │ ├── 10131000171211950000.png │ ├── 10133000001100000002.png │ ├── CAN_Flag.png │ ├── NATO.jpg │ ├── NATO_Flag.png │ ├── NLD_Flag.png │ ├── SFG-U--------US.png │ ├── SFGP---------US.png │ ├── USA_Flag.png │ ├── favicon.png │ └── tacsym.png ├── js │ ├── lib │ │ ├── compression │ │ │ ├── base64-string-v1.1.0.js │ │ │ └── lz-string-1.3.3-min.js │ │ ├── indexdb │ │ │ ├── angular-indexed-db.js │ │ │ ├── angular-indexed-db.min.js │ │ │ └── angular-indexed-db.min.js.map │ │ └── xml2json.min.js │ ├── tacmapServer.js │ ├── tacmapUnit.js │ ├── tmSrvrControllers.js │ ├── tmSrvrServices.js │ ├── tmUnitControllers.js │ └── tmUnitServices.js ├── json │ ├── defaultmission.json │ ├── missions.json │ └── trap.json ├── server.html ├── unit.html └── xml │ ├── CAMPEN.xml │ ├── DefaultMission.xml │ ├── TRAP.xml │ ├── gpx.xsd │ ├── missions.xml │ ├── speeds.xml │ └── symbols.xml ├── tacmap.js └── web.config /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | RUN useradd --create-home -s /bin/bash tacmap 3 | WORKDIR /home/tacmap 4 | COPY . /home/tacmap 5 | RUN rm -Rf /home/tacmap/node_modules 6 | ADD package.json /tmp/package.json 7 | RUN cd /tmp && npm install 8 | RUN cp -a /tmp/node_modules /home/tacmap 9 | RUN cd /home/tacmap 10 | CMD node tacmap.js --public 11 | EXPOSE 8080 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### TacMap 2 | 3 | TacMap is a browser based track generation and communication tool which allows the designation of movement paths for simulated entities and periodic reporting of positions over time 4 | 5 | ### Live Demo: 6 | * https://tacmap.neushul.net/server 7 | * https://tacmap.neushul.net/unit 8 | 9 | ### Project Description 10 | * This is a prototype work in progress 11 | * This project was developed to generate sample tracks for use in MIL STD messaging testing 12 | * All messages are generated, consumed and stored using Javascript in browsers. 13 | * Data persistence is achieved using HTML5 IndexedDb - which allows disconnected functionality after initialization. 14 | * Ground truth simulation is achieved using planned tracks which are interpreted as periodic position reports and communicated as JSON objects 15 | * JSON Objects can be converted into MIL STD message XML formats and converted to and from other formats using SAXON CE XSLT in the browser. This functionality was demonstrated with NATO MTF FFI messages at CWIX 2015 and disseminated using the NATO SIP3 SOAP based protocol.. this code is not included but please make contact if there is interest in it. 16 | * A NodeJS server with SocketIO provides initialization with Javascript code and basic message relay without storage or other traditional server functions. Socket.io is an event-driven Javascript library for real-time communications based on the WebSocket protocol. 17 | * The NodeJS server app implements an AngularJS module, largely to enforce a Model-View-Controller (MVC) pattern. 18 | * The browser client implements Javascript library Cesium for 3D mapping capabilities. 19 | * The functionality has been tested using Firefox and Chrome browsers. 20 | 21 | ### Way Forward 22 | Future collaborative work may include: 23 | * The communication of MIL-STD VMF binary messages directly to and from the browser client 24 | * The creation of MIL-STD messages such as VMF and MTF by user interactions with the visualization client and mapping capabilities. 25 | * Implementation of client/unit level track adjustments for Wargaming. 26 | * Implementation of proximity and visibility algorithms for Communication planning and Wargaming. 27 | * Packaging as an Android App for use as a lightweight tactical position reporting and display as a Combat Relevant PLI capability for individual operators and small units. 28 | * Unit tests ! 29 | 30 | ### Run the Current Demo 31 | 32 | To run the demo: 33 | * Install Node.js 34 | * Check out project 35 | * In the TacMap directory type "npm install" 36 | * In the TacMap directory type "node tacmap.js" 37 | * Use an HTML5 compatible browser to go to "http://localhost:8585/server" 38 | * Use an HTML5 compatible browser to go to "http://localhost:8585/unit" 39 | * To import a scenario without planned paths use the "import" button in the Plan Scenario and select "Malmo" 40 | * To select a scenario with planned paths use the "import" button in the Plan Scenario tab and select "Bydgoszcz" 41 | * These Scenarios will be saved in the browser IndexedDB for selection under the Scenarios tab 42 | * Use Set Location and Edit Waypoints check boxes with a selected unit to plan a scenario choose a speed to associate with movement segments. 43 | * Click RUN to start movements. Positions will be updated on the Server view and on any connected clients. 44 | * The Networks buttons will select or obscure associated units 45 | * Units and Networks are assigned in the defaultScenario.xml file. To create new scenarios adjust this file and save from that. 46 | * The "Save" operation overwrites or "Saves As" a new Scenario in the IndexedDB local storage of the browser. 47 | * The "Export" and "Import" functions save and load JSON data to the server for persistence external to the browser. 48 | 49 | ### Components Employed ### 50 | * Cesium Web Map 51 | * AngularJS Framework 52 | * HTML5 IndexedDb 53 | * SAXON CE XSLT library 54 | * Node.js 55 | * SocketIO 56 | 57 | ### License ### 58 | 59 | [GPL 3.0](http://fsf.org/). 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/NetArchitecture.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/doc/NetArchitecture.odp -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/favicon.ico -------------------------------------------------------------------------------- /geoserver/cesiumserver.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | /*global console,require,__dirname,process*/ 4 | /*jshint es3:false*/ 5 | 6 | var express = require('express'); 7 | var url = require('url'); 8 | var request = require('axios'); 9 | var mime = express.static.mime; 10 | mime.define({ 11 | 'application/json': ['czml', 'json', 'geojson', 'topojson', 'gltf'], 12 | 'text/plain': ['glsl'] 13 | }); 14 | function getRemoteUrlFromParam(req) { 15 | var remoteUrl = req.params[0]; 16 | if (remoteUrl) { 17 | // add http:// to the URL if no protocol is present 18 | if (!/^https?:\/\//.test(remoteUrl)) { 19 | remoteUrl = 'http://' + remoteUrl; 20 | } 21 | remoteUrl = new URL(remoteUrl); 22 | // copy query string 23 | remoteUrl.search = new URL(req.url).search; 24 | } 25 | return remoteUrl; 26 | } 27 | var dontProxyHeaderRegex = /^(?:Host|Proxy-Connection|Connection|Keep-Alive|Transfer-Encoding|TE|Trailer|Proxy-Authorization|Proxy-Authenticate|Upgrade)$/i; 28 | function filterHeaders(req, headers) { 29 | var result = {}; 30 | // filter out headers that are listed in the regex above 31 | Object.keys(headers).forEach(function (name) { 32 | if (!dontProxyHeaderRegex.test(name)) { 33 | result[name] = headers[name]; 34 | } 35 | }); 36 | return result; 37 | } 38 | var upstreamProxy = argv['upstream-proxy']; 39 | var bypassUpstreamProxyHosts = {}; 40 | if (argv['bypass-upstream-proxy-hosts']) { 41 | argv['bypass-upstream-proxy-hosts'].split(',').forEach(function (host) { 42 | bypassUpstreamProxyHosts[host.toLowerCase()] = true; 43 | }); 44 | } 45 | app.get('/proxy/*', function (req, res, next) { 46 | // look for request like http://localhost:8080/proxy/http://example.com/file?query=1 47 | var remoteUrl = getRemoteUrlFromParam(req); 48 | if (!remoteUrl) { 49 | // look for request like http://localhost:8080/proxy/?http%3A%2F%2Fexample.com%2Ffile%3Fquery%3D1 50 | remoteUrl = Object.keys(req.query)[0]; 51 | if (remoteUrl) { 52 | remoteUrl = new URL(remoteUrl); 53 | } 54 | } 55 | 56 | if (!remoteUrl) { 57 | return res.send(400, 'No url specified.'); 58 | } 59 | 60 | if (!remoteUrl.protocol) { 61 | remoteUrl.protocol = 'http:'; 62 | } 63 | 64 | var proxy; 65 | if (upstreamProxy && !(remoteUrl.host in bypassUpstreamProxyHosts)) { 66 | proxy = upstreamProxy; 67 | } 68 | 69 | // encoding : null means "body" passed to the callback will be raw bytes 70 | 71 | request.get({ 72 | url: url.format(remoteUrl), 73 | headers: filterHeaders(req, req.headers), 74 | encoding: null, 75 | proxy: proxy 76 | }, function (error, response, body) { 77 | var code = 500; 78 | 79 | if (response) { 80 | code = response.statusCode; 81 | res.header(filterHeaders(req, response.headers)); 82 | } 83 | 84 | res.send(code, body); 85 | }); 86 | }); 87 | 88 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tacmap", 3 | "version": "0.0.2", 4 | "description": "Simulation using Cesium JavaScript library to visualize PLI message traffic for moving units on a map.", 5 | "homepage": "https://github.com/mil-oss/TacMap.git", 6 | "license": "GPL-3.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/mil-oss/TacMap.git" 10 | }, 11 | "main": "tacmap.js", 12 | "scripts": { 13 | "start": "node tacmap.js --public" 14 | }, 15 | "devDependencies": { 16 | "axios": "^1.7.7", 17 | "compression": "1.7.4", 18 | "socket.io-client": "4.8.0" 19 | }, 20 | "dependencies": { 21 | "angular": "1.8.3", 22 | "cesium": "1.122.0", 23 | "eslint": "9.12.0", 24 | "socket.io": "4.8.0", 25 | "yargs": "17.7.2" 26 | }, 27 | "author": "James Neushul", 28 | "bugs": { 29 | "url": "https://github.com/mil-oss/TacMap/issues" 30 | }, 31 | "directories": { 32 | "doc": "doc", 33 | "test": "test" 34 | } 35 | } -------------------------------------------------------------------------------- /public/css/plimap.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) 2015 Major James Neushul, USMC 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | html{ 21 | height:100%; 22 | } 23 | body{ 24 | padding:0; 25 | margin:0; 26 | overflow:hidden; 27 | height:100%; 28 | background-color:black; 29 | } 30 | /*Remove button padding in FF*/ 31 | button::-moz-focus-inner { 32 | border:0; 33 | padding:0; 34 | } 35 | 36 | #cesiumContainer{ 37 | position:absolute; 38 | left:180px; 39 | height:100%; 40 | width:86%; 41 | margin:0; 42 | overflow:hidden; 43 | padding:0; 44 | font-family:sans-serif; 45 | } 46 | #infoCol{ 47 | position:absolute; 48 | left:0; 49 | top:0; 50 | height:100%; 51 | width:180px; 52 | margin:2px; 53 | } 54 | .sect{ 55 | width:100%; 56 | margin-top:5px; 57 | padding-top:5px; 58 | border-top:thin solid black; 59 | font-size:10px; 60 | } 61 | .rptlist{ 62 | height:300px; 63 | overflow-y: auto; 64 | } 65 | .rptlsting{ 66 | font-size:12px; 67 | font-weight: bold; 68 | width:98%; 69 | height:50px; 70 | border:thin solid black; 71 | text-align:center; 72 | margin-top:5px; 73 | } 74 | .rptunit{ 75 | text-align:center; 76 | } 77 | .rptsel{ 78 | font-size:12px; 79 | margin-top:3px; 80 | width:120px; 81 | text-indent: 10px; 82 | } 83 | .rptsect{ 84 | width:100%; 85 | margin-top:5px; 86 | font-size:10px; 87 | } 88 | .msglisting{ 89 | font-size:9px; 90 | font-weight: normal; 91 | } 92 | .missiontitle{ 93 | color:yellow; 94 | font-weight:bold; 95 | font-size:18px; 96 | } 97 | select{ 98 | font-size:12px; 99 | width:100%; 100 | } 101 | option{ 102 | font-size:12px; 103 | } 104 | .rptsel{ 105 | font-size:10px; 106 | font-weight: bold; 107 | } 108 | .rptsel span{ 109 | width:50%; 110 | } 111 | .rptsel select{ 112 | font-size:10px; 113 | width:50%; 114 | float:right; 115 | } 116 | .msect{ 117 | margin-top:4px; 118 | font-style:italic; 119 | text-align:center; 120 | } 121 | .label{ 122 | margin:4px; 123 | font-style:italic; 124 | text-align:left; 125 | } 126 | .bsect{ 127 | padding-top:5px; 128 | border-top:thin solid black; 129 | height:20px; 130 | width:90%; 131 | margin: 0 auto; 132 | padding-bottom:15px; 133 | } 134 | .bsect input{ 135 | margin-top:5px; 136 | } 137 | .bspc{ 138 | margin-bottom:5px; 139 | } 140 | .topspc{ 141 | margin-top:5px; 142 | } 143 | .smbtn{ 144 | padding:0; 145 | font-size:10px; 146 | color:blue; 147 | cursor:pointer; 148 | margin-left:1px; 149 | } 150 | .bottomdiv{ 151 | position:absolute; 152 | bottom:5px; 153 | width:160px; 154 | } 155 | .txtbtn{ 156 | color:red; 157 | margin-top: 3px; 158 | width:160px; 159 | text-align:center; 160 | cursor:pointer; 161 | font-size:10px; 162 | } 163 | .status{ 164 | position:absolute; 165 | bottom:25px; 166 | width:160px; 167 | color:white; 168 | text-align:center; 169 | } 170 | .btn{ 171 | padding:0; 172 | font-size:10px; 173 | color:blue; 174 | cursor:pointer; 175 | margin-left:25px; 176 | } 177 | .hidden{ 178 | display:none; 179 | } 180 | .selected{ 181 | background:yellow; 182 | } 183 | .wpchkbx{ 184 | margin-left:10px; 185 | } 186 | .units{ 187 | width:154px; 188 | margin-right:2px; 189 | max-height:400px; 190 | overflow-y:auto; 191 | } 192 | .unitlisting img{ 193 | margin-left:3px; 194 | margin-right:3px; 195 | float:left; 196 | height:20px; 197 | } 198 | .unitlisting{ 199 | width:90%; 200 | height:20px; 201 | cursor:pointer; 202 | border:thin solid black; 203 | border-radius:2px; 204 | padding:2px; 205 | box-shadow:2px 2px 2px #888888; 206 | margin:2px; 207 | text-align:left; 208 | font-weight:normal; 209 | } 210 | .unitlabel{ 211 | margin-left:5px; 212 | padding-left:5px; 213 | padding-top:8px; 214 | } 215 | .accordion__container{ 216 | background:#FFF; 217 | border:1px solid #ccc; 218 | /* padding: 2px; */ 219 | } 220 | .accordion__tab{ 221 | background:#aaa; 222 | color:#000; 223 | border-bottom:1px solid white; 224 | font-size:10px; 225 | font-weight:bold; 226 | } 227 | .accordion__tab-title{ 228 | background:#BEDFD5; 229 | padding:5px; 230 | cursor:pointer; 231 | } 232 | .accordion__tab-content{ 233 | background:#FFF; 234 | padding:2px; 235 | } 236 | .filelist { 237 | width:145px; 238 | margin:2px; 239 | border:solid black thin; 240 | min-height:75px; 241 | max-height:150px; 242 | overflow-y:auto; 243 | cursor:not-allowed; 244 | } 245 | .filelisting{ 246 | padding:3px; 247 | cursor:pointer; 248 | color:blue; 249 | } 250 | .noselect { 251 | -webkit-touch-callout: none; 252 | -webkit-user-select: none; 253 | -khtml-user-select: none; 254 | -moz-user-select: none; 255 | -ms-user-select: none; 256 | user-select: none; 257 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/favicon.ico -------------------------------------------------------------------------------- /public/img/10130100000000000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10130100000000000000.png -------------------------------------------------------------------------------- /public/img/10130100001102000802.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10130100001102000802.png -------------------------------------------------------------------------------- /public/img/10130100001103000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10130100001103000000.png -------------------------------------------------------------------------------- /public/img/10131000000000000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000000000000000.png -------------------------------------------------------------------------------- /public/img/10131000001900000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000001900000000.png -------------------------------------------------------------------------------- /public/img/10131000101212000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000101212000000.png -------------------------------------------------------------------------------- /public/img/10131000111211000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000111211000000.png -------------------------------------------------------------------------------- /public/img/10131000121211000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000121211000000.png -------------------------------------------------------------------------------- /public/img/10131000121211040000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000121211040000.png -------------------------------------------------------------------------------- /public/img/10131000141211000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000141211000000.png -------------------------------------------------------------------------------- /public/img/10131000141211950000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000141211950000.png -------------------------------------------------------------------------------- /public/img/10131000151211950000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000151211950000.png -------------------------------------------------------------------------------- /public/img/10131000161211950000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000161211950000.png -------------------------------------------------------------------------------- /public/img/10131000171211950000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10131000171211950000.png -------------------------------------------------------------------------------- /public/img/10133000001100000002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/10133000001100000002.png -------------------------------------------------------------------------------- /public/img/CAN_Flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/CAN_Flag.png -------------------------------------------------------------------------------- /public/img/NATO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/NATO.jpg -------------------------------------------------------------------------------- /public/img/NATO_Flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/NATO_Flag.png -------------------------------------------------------------------------------- /public/img/NLD_Flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/NLD_Flag.png -------------------------------------------------------------------------------- /public/img/SFG-U--------US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/SFG-U--------US.png -------------------------------------------------------------------------------- /public/img/SFGP---------US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/SFGP---------US.png -------------------------------------------------------------------------------- /public/img/USA_Flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/USA_Flag.png -------------------------------------------------------------------------------- /public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/favicon.png -------------------------------------------------------------------------------- /public/img/tacsym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neutron/TacMap/06d1df4f1d9814a75393ef8c2e667dc01c0b498a/public/img/tacsym.png -------------------------------------------------------------------------------- /public/js/lib/compression/base64-string-v1.1.0.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Pieroxy 2 | // This work is free. You can redistribute it and/or modify it 3 | // under the terms of the WTFPL, Version 2 4 | // For more information see LICENSE.txt or http://www.wtfpl.net/ 5 | // 6 | // This lib is part of the lz-string project. 7 | // For more information, the home page: 8 | // http://pieroxy.net/blog/pages/lz-string/index.html 9 | // 10 | // Base64 compression / decompression for already compressed content (gif, png, jpg, mp3, ...) 11 | // version 1.1.0 12 | var Base64String = { 13 | 14 | compressToUTF16 : function (input) { 15 | var output = "", 16 | i,c, 17 | current, 18 | status = 0; 19 | 20 | input = this.compress(input); 21 | 22 | for (i=0 ; i> 1)+32); 27 | current = (c & 1) << 14; 28 | break; 29 | case 1: 30 | output += String.fromCharCode((current + (c >> 2))+32); 31 | current = (c & 3) << 13; 32 | break; 33 | case 2: 34 | output += String.fromCharCode((current + (c >> 3))+32); 35 | current = (c & 7) << 12; 36 | break; 37 | case 3: 38 | output += String.fromCharCode((current + (c >> 4))+32); 39 | current = (c & 15) << 11; 40 | break; 41 | case 4: 42 | output += String.fromCharCode((current + (c >> 5))+32); 43 | current = (c & 31) << 10; 44 | break; 45 | case 5: 46 | output += String.fromCharCode((current + (c >> 6))+32); 47 | current = (c & 63) << 9; 48 | break; 49 | case 6: 50 | output += String.fromCharCode((current + (c >> 7))+32); 51 | current = (c & 127) << 8; 52 | break; 53 | case 7: 54 | output += String.fromCharCode((current + (c >> 8))+32); 55 | current = (c & 255) << 7; 56 | break; 57 | case 8: 58 | output += String.fromCharCode((current + (c >> 9))+32); 59 | current = (c & 511) << 6; 60 | break; 61 | case 9: 62 | output += String.fromCharCode((current + (c >> 10))+32); 63 | current = (c & 1023) << 5; 64 | break; 65 | case 10: 66 | output += String.fromCharCode((current + (c >> 11))+32); 67 | current = (c & 2047) << 4; 68 | break; 69 | case 11: 70 | output += String.fromCharCode((current + (c >> 12))+32); 71 | current = (c & 4095) << 3; 72 | break; 73 | case 12: 74 | output += String.fromCharCode((current + (c >> 13))+32); 75 | current = (c & 8191) << 2; 76 | break; 77 | case 13: 78 | output += String.fromCharCode((current + (c >> 14))+32); 79 | current = (c & 16383) << 1; 80 | break; 81 | case 14: 82 | output += String.fromCharCode((current + (c >> 15))+32, (c & 32767)+32); 83 | status = 0; 84 | break; 85 | } 86 | } 87 | 88 | return output + String.fromCharCode(current + 32); 89 | }, 90 | 91 | 92 | decompressFromUTF16 : function (input) { 93 | var output = "", 94 | current,c, 95 | status=0, 96 | i = 0; 97 | 98 | while (i < input.length) { 99 | c = input.charCodeAt(i) - 32; 100 | 101 | switch (status++) { 102 | case 0: 103 | current = c << 1; 104 | break; 105 | case 1: 106 | output += String.fromCharCode(current | (c >> 14)); 107 | current = (c&16383) << 2; 108 | break; 109 | case 2: 110 | output += String.fromCharCode(current | (c >> 13)); 111 | current = (c&8191) << 3; 112 | break; 113 | case 3: 114 | output += String.fromCharCode(current | (c >> 12)); 115 | current = (c&4095) << 4; 116 | break; 117 | case 4: 118 | output += String.fromCharCode(current | (c >> 11)); 119 | current = (c&2047) << 5; 120 | break; 121 | case 5: 122 | output += String.fromCharCode(current | (c >> 10)); 123 | current = (c&1023) << 6; 124 | break; 125 | case 6: 126 | output += String.fromCharCode(current | (c >> 9)); 127 | current = (c&511) << 7; 128 | break; 129 | case 7: 130 | output += String.fromCharCode(current | (c >> 8)); 131 | current = (c&255) << 8; 132 | break; 133 | case 8: 134 | output += String.fromCharCode(current | (c >> 7)); 135 | current = (c&127) << 9; 136 | break; 137 | case 9: 138 | output += String.fromCharCode(current | (c >> 6)); 139 | current = (c&63) << 10; 140 | break; 141 | case 10: 142 | output += String.fromCharCode(current | (c >> 5)); 143 | current = (c&31) << 11; 144 | break; 145 | case 11: 146 | output += String.fromCharCode(current | (c >> 4)); 147 | current = (c&15) << 12; 148 | break; 149 | case 12: 150 | output += String.fromCharCode(current | (c >> 3)); 151 | current = (c&7) << 13; 152 | break; 153 | case 13: 154 | output += String.fromCharCode(current | (c >> 2)); 155 | current = (c&3) << 14; 156 | break; 157 | case 14: 158 | output += String.fromCharCode(current | (c >> 1)); 159 | current = (c&1) << 15; 160 | break; 161 | case 15: 162 | output += String.fromCharCode(current | c); 163 | status=0; 164 | break; 165 | } 166 | 167 | 168 | i++; 169 | } 170 | 171 | return this.decompress(output); 172 | //return output; 173 | 174 | }, 175 | 176 | 177 | // private property 178 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 179 | 180 | decompress : function (input) { 181 | var output = ""; 182 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 183 | var i = 1; 184 | var odd = input.charCodeAt(0) >> 8; 185 | 186 | while (i < input.length*2 && (i < input.length*2-1 || odd==0)) { 187 | 188 | if (i%2==0) { 189 | chr1 = input.charCodeAt(i/2) >> 8; 190 | chr2 = input.charCodeAt(i/2) & 255; 191 | if (i/2+1 < input.length) 192 | chr3 = input.charCodeAt(i/2+1) >> 8; 193 | else 194 | chr3 = NaN; 195 | } else { 196 | chr1 = input.charCodeAt((i-1)/2) & 255; 197 | if ((i+1)/2 < input.length) { 198 | chr2 = input.charCodeAt((i+1)/2) >> 8; 199 | chr3 = input.charCodeAt((i+1)/2) & 255; 200 | } else 201 | chr2=chr3=NaN; 202 | } 203 | i+=3; 204 | 205 | enc1 = chr1 >> 2; 206 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 207 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 208 | enc4 = chr3 & 63; 209 | 210 | if (isNaN(chr2) || (i==input.length*2+1 && odd)) { 211 | enc3 = enc4 = 64; 212 | } else if (isNaN(chr3) || (i==input.length*2 && odd)) { 213 | enc4 = 64; 214 | } 215 | 216 | output = output + 217 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + 218 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 219 | 220 | } 221 | 222 | return output; 223 | }, 224 | 225 | compress : function (input) { 226 | var output = "", 227 | ol = 1, 228 | output_, 229 | chr1, chr2, chr3, 230 | enc1, enc2, enc3, enc4, 231 | i = 0, flush=false; 232 | 233 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 234 | 235 | while (i < input.length) { 236 | 237 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 238 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 239 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 240 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 241 | 242 | chr1 = (enc1 << 2) | (enc2 >> 4); 243 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 244 | chr3 = ((enc3 & 3) << 6) | enc4; 245 | 246 | if (ol%2==0) { 247 | output_ = chr1 << 8; 248 | flush = true; 249 | 250 | if (enc3 != 64) { 251 | output += String.fromCharCode(output_ | chr2); 252 | flush = false; 253 | } 254 | if (enc4 != 64) { 255 | output_ = chr3 << 8; 256 | flush = true; 257 | } 258 | } else { 259 | output = output + String.fromCharCode(output_ | chr1); 260 | flush = false; 261 | 262 | if (enc3 != 64) { 263 | output_ = chr2 << 8; 264 | flush = true; 265 | } 266 | if (enc4 != 64) { 267 | output += String.fromCharCode(output_ | chr3); 268 | flush = false; 269 | } 270 | } 271 | ol+=3; 272 | } 273 | 274 | if (flush) { 275 | output += String.fromCharCode(output_); 276 | output = String.fromCharCode(output.charCodeAt(0)|256) + output.substring(1); 277 | } 278 | 279 | return output; 280 | 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /public/js/lib/compression/lz-string-1.3.3-min.js: -------------------------------------------------------------------------------- 1 | var LZString={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_f:String.fromCharCode,compressToBase64:function(e){if(e==null)return"";var t="";var n,r,i,s,o,u,a;var f=0;e=LZString.compress(e);while(f>8;r=e.charCodeAt(f/2)&255;if(f/2+1>8;else i=NaN}else{n=e.charCodeAt((f-1)/2)&255;if((f+1)/2>8;i=e.charCodeAt((f+1)/2)&255}else r=i=NaN}f+=3;s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+LZString._keyStr.charAt(s)+LZString._keyStr.charAt(o)+LZString._keyStr.charAt(u)+LZString._keyStr.charAt(a)}return t},decompressFromBase64:function(e){if(e==null)return"";var t="",n=0,r,i,s,o,u,a,f,l,c=0,h=LZString._f;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(c>4;s=(a&15)<<4|f>>2;o=(f&3)<<6|l;if(n%2==0){r=i<<8;if(f!=64){t+=h(r|s)}if(l!=64){r=o<<8}}else{t=t+h(r|i);if(f!=64){r=s<<8}if(l!=64){t+=h(r|o)}}n+=3}return LZString.decompress(t)},compressToUTF16:function(e){if(e==null)return"";var t="",n,r,i,s=0,o=LZString._f;e=LZString.compress(e);for(n=0;n>1)+32);i=(r&1)<<14;break;case 1:t+=o(i+(r>>2)+32);i=(r&3)<<13;break;case 2:t+=o(i+(r>>3)+32);i=(r&7)<<12;break;case 3:t+=o(i+(r>>4)+32);i=(r&15)<<11;break;case 4:t+=o(i+(r>>5)+32);i=(r&31)<<10;break;case 5:t+=o(i+(r>>6)+32);i=(r&63)<<9;break;case 6:t+=o(i+(r>>7)+32);i=(r&127)<<8;break;case 7:t+=o(i+(r>>8)+32);i=(r&255)<<7;break;case 8:t+=o(i+(r>>9)+32);i=(r&511)<<6;break;case 9:t+=o(i+(r>>10)+32);i=(r&1023)<<5;break;case 10:t+=o(i+(r>>11)+32);i=(r&2047)<<4;break;case 11:t+=o(i+(r>>12)+32);i=(r&4095)<<3;break;case 12:t+=o(i+(r>>13)+32);i=(r&8191)<<2;break;case 13:t+=o(i+(r>>14)+32);i=(r&16383)<<1;break;case 14:t+=o(i+(r>>15)+32,(r&32767)+32);s=0;break}}return t+o(i+32)},decompressFromUTF16:function(e){if(e==null)return"";var t="",n,r,i=0,s=0,o=LZString._f;while(s>14);n=(r&16383)<<2;break;case 2:t+=o(n|r>>13);n=(r&8191)<<3;break;case 3:t+=o(n|r>>12);n=(r&4095)<<4;break;case 4:t+=o(n|r>>11);n=(r&2047)<<5;break;case 5:t+=o(n|r>>10);n=(r&1023)<<6;break;case 6:t+=o(n|r>>9);n=(r&511)<<7;break;case 7:t+=o(n|r>>8);n=(r&255)<<8;break;case 8:t+=o(n|r>>7);n=(r&127)<<9;break;case 9:t+=o(n|r>>6);n=(r&63)<<10;break;case 10:t+=o(n|r>>5);n=(r&31)<<11;break;case 11:t+=o(n|r>>4);n=(r&15)<<12;break;case 12:t+=o(n|r>>3);n=(r&7)<<13;break;case 13:t+=o(n|r>>2);n=(r&3)<<14;break;case 14:t+=o(n|r>>1);n=(r&1)<<15;break;case 15:t+=o(n|r);i=0;break}s++}return LZString.decompress(t)},compress:function(e){if(e==null)return"";var t,n,r={},i={},s="",o="",u="",a=2,f=3,l=2,c="",h=0,p=0,d,v=LZString._f;for(d=0;d>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}r[o]=f++;u=String(s)}}if(u!==""){if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}}n=2;for(t=0;t>1}while(true){h=h<<1;if(p==15){c+=v(h);break}else p++}return c},decompress:function(e){if(e==null)return"";if(e=="")return null;var t=[],n,r=4,i=4,s=3,o="",u="",a,f,l,c,h,p,d,v=LZString._f,m={string:e,val:e.charCodeAt(0),position:32768,index:1};for(a=0;a<3;a+=1){t[a]=a}l=0;h=Math.pow(2,2);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(n=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 2:return""}t[3]=d;f=u=d;while(true){if(m.index>m.string.length){return""}l=0;h=Math.pow(2,s);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(d=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 2:return u}if(r==0){r=Math.pow(2,s);s++}if(t[d]){o=t[d]}else{if(d===i){o=f+f.charAt(0)}else{return null}}u+=o;t[i++]=f+o.charAt(0);r--;f=o;if(r==0){r=Math.pow(2,s);s++}}}};if(typeof module!=="undefined"&&module!=null){module.exports=LZString} 2 | -------------------------------------------------------------------------------- /public/js/lib/indexdb/angular-indexed-db.min.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";var __slice=[].slice;angular.module("indexedDB",[]).provider("$indexedDB",function(){var IDBKeyRange,allTransactions,apiDirection,appendResultsToPromise,applyNeededUpgrades,cursorDirection,db,dbMode,dbName,dbPromise,dbVersion,defaultQueryOptions,errorMessageFor,indexedDB,readyState,upgradesByVersion;indexedDB=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,IDBKeyRange=window.IDBKeyRange||window.mozIDBKeyRange||window.webkitIDBKeyRange||window.msIDBKeyRange,dbMode={readonly:"readonly",readwrite:"readwrite"},readyState={pending:"pending"},cursorDirection={next:"next",nextunique:"nextunique",prev:"prev",prevunique:"prevunique"},apiDirection={ascending:cursorDirection.next,descending:cursorDirection.prev},dbName="",dbVersion=1,db=null,upgradesByVersion={},dbPromise=null,allTransactions=[],defaultQueryOptions={useIndex:void 0,keyRange:null,direction:cursorDirection.next},applyNeededUpgrades=function(oldVersion,event,db,tx){var version;for(version in upgradesByVersion)!upgradesByVersion.hasOwnProperty(version)||oldVersion>=version||(console.debug("$indexedDB: Running upgrade : "+version+" from "+oldVersion),upgradesByVersion[version](event,db,tx))},errorMessageFor=function(e){return e.target.readyState===readyState.pending?"Error: Operation pending":e.target.webkitErrorMessage||e.target.error.message||e.target.errorCode},appendResultsToPromise=function(promise,results){return void 0!==results?promise.then(function(){return results}):promise},this.connection=function(databaseName){return dbName=databaseName,this},this.upgradeDatabase=function(newVersion,callback){return upgradesByVersion[newVersion]=callback,dbVersion=Math.max.apply(null,Object.keys(upgradesByVersion)),this},this.$get=["$q","$rootScope",function($q,$rootScope){var DbQ,ObjectStore,Query,Transaction,addTransaction,closeDatabase,createDatabaseConnection,keyRangeForOptions,openDatabase,openTransaction,rejectWithError,validateStoreNames;return rejectWithError=function(deferred){return function(error){return $rootScope.$apply(function(){return deferred.reject(errorMessageFor(error))})}},createDatabaseConnection=function(){var dbReq,deferred;return deferred=$q.defer(),dbReq=indexedDB.open(dbName,dbVersion||1),dbReq.onsuccess=function(){db=dbReq.result,$rootScope.$apply(function(){deferred.resolve(db)})},dbReq.onblocked=dbReq.onerror=rejectWithError(deferred),dbReq.onupgradeneeded=function(event){var tx;db=event.target.result,tx=event.target.transaction,console.debug("$indexedDB: Upgrading database '"+db.name+"' from version "+event.oldVersion+" to version "+event.newVersion+" ..."),applyNeededUpgrades(event.oldVersion,event,db,tx)},deferred.promise},openDatabase=function(){return dbPromise||(dbPromise=createDatabaseConnection())},closeDatabase=function(){return openDatabase().then(function(){return db.close(),db=null,dbPromise=null})},validateStoreNames=function(storeNames){var found,storeName,_i,_len;for(found=!0,_i=0,_len=storeNames.length;_len>_i;_i++)storeName=storeNames[_i],found&=db.objectStoreNames.contains(storeName);return found},openTransaction=function(storeNames,mode){return null==mode&&(mode=dbMode.readonly),openDatabase().then(function(){return validateStoreNames(storeNames)?new Transaction(storeNames,mode):$q.reject("Object stores "+storeNames+" do not exist.")})},keyRangeForOptions=function(options){return options.beginKey&&options.endKey?IDBKeyRange.bound(options.beginKey,options.endKey):void 0},addTransaction=function(transaction){return allTransactions.push(transaction.promise),transaction.promise["finally"](function(){var index;return index=allTransactions.indexOf(transaction.promise),index>-1?allTransactions.splice(index,1):void 0})},Transaction=function(){function Transaction(storeNames,mode){null==mode&&(mode=dbMode.readonly),this.transaction=db.transaction(storeNames,mode),this.defer=$q.defer(),this.promise=this.defer.promise,this.setupCallbacks()}return Transaction.prototype.setupCallbacks=function(){return this.transaction.oncomplete=function(_this){return function(){return $rootScope.$apply(function(){return _this.defer.resolve("Transaction Completed")})}}(this),this.transaction.onabort=function(_this){return function(error){return $rootScope.$apply(function(){return _this.defer.reject("Transaction Aborted",error)})}}(this),this.transaction.onerror=function(_this){return function(error){return $rootScope.$apply(function(){return _this.defer.reject("Transaction Error",error)})}}(this),addTransaction(this)},Transaction.prototype.objectStore=function(storeName){return this.transaction.objectStore(storeName)},Transaction.prototype.abort=function(){return this.transaction.abort()},Transaction}(),DbQ=function(){function DbQ(){this.q=$q.defer(),this.promise=this.q.promise}return DbQ.prototype.reject=function(){var args;return args=1<=arguments.length?__slice.call(arguments,0):[],$rootScope.$apply(function(_this){return function(){var _ref;return(_ref=_this.q).reject.apply(_ref,args)}}(this))},DbQ.prototype.rejectWith=function(req){return req.onerror=req.onblocked=function(_this){return function(e){return _this.reject(errorMessageFor(e))}}(this)},DbQ.prototype.resolve=function(){var args;return args=1<=arguments.length?__slice.call(arguments,0):[],$rootScope.$apply(function(_this){return function(){var _ref;return(_ref=_this.q).resolve.apply(_ref,args)}}(this))},DbQ.prototype.notify=function(){var args;return args=1<=arguments.length?__slice.call(arguments,0):[],$rootScope.$apply(function(_this){return function(){var _ref;return(_ref=_this.q).notify.apply(_ref,args)}}(this))},DbQ.prototype.dbErrorFunction=function(){return function(_this){return function(error){return $rootScope.$apply(function(){return _this.q.reject(errorMessageFor(error))})}}(this)},DbQ.prototype.resolveWith=function(req){return this.rejectWith(req),req.onsuccess=function(_this){return function(e){return _this.resolve(e.target.result)}}(this)},DbQ}(),ObjectStore=function(){function ObjectStore(storeName,transaction){this.storeName=storeName,this.store=transaction.objectStore(storeName),this.transaction=transaction}return ObjectStore.prototype.defer=function(){return new DbQ},ObjectStore.prototype._mapCursor=function(defer,mapFunc,req){var results;return null==req&&(req=this.store.openCursor()),results=[],defer.rejectWith(req),req.onsuccess=function(e){var cursor;return(cursor=e.target.result)?(results.push(mapFunc(cursor)),defer.notify(mapFunc(cursor)),cursor["continue"]()):defer.resolve(results)}},ObjectStore.prototype._arrayOperation=function(data,mapFunc){var defer,item,req,results,_i,_len;for(defer=this.defer(),angular.isArray(data)||(data=[data]),_i=0,_len=data.length;_len>_i;_i++)item=data[_i],req=mapFunc(item),results=[],defer.rejectWith(req),req.onsuccess=function(e){return results.push(e.target.result),defer.notify(e.target.result),results.length>=data.length?defer.resolve(results):void 0};return 0===data.length?$q.when([]):defer.promise},ObjectStore.prototype.getAllKeys=function(){var defer,req;return defer=this.defer(),this.store.getAllKeys?(req=this.store.getAllKeys(),defer.resolveWith(req)):this._mapCursor(defer,function(cursor){return cursor.key}),defer.promise},ObjectStore.prototype.clear=function(){var defer,req;return defer=this.defer(),req=this.store.clear(),defer.resolveWith(req),defer.promise},ObjectStore.prototype["delete"]=function(key){var defer;return defer=this.defer(),defer.resolveWith(this.store["delete"](key)),defer.promise},ObjectStore.prototype.upsert=function(data){return this._arrayOperation(data,function(_this){return function(item){return _this.store.put(item)}}(this))},ObjectStore.prototype.insert=function(data){return this._arrayOperation(data,function(_this){return function(item){return _this.store.add(item)}}(this))},ObjectStore.prototype.getAll=function(){var defer;return defer=this.defer(),this.store.getAll?defer.resolveWith(this.store.getAll()):this._mapCursor(defer,function(cursor){return cursor.value}),defer.promise},ObjectStore.prototype.eachWhere=function(query){var defer,direction,indexName,keyRange,req;return defer=this.defer(),indexName=query.indexName,keyRange=query.keyRange,direction=query.direction,req=indexName?this.store.index(indexName).openCursor(keyRange,direction):this.store.openCursor(keyRange,direction),this._mapCursor(defer,function(cursor){return cursor.value},req),defer.promise},ObjectStore.prototype.findWhere=function(query){return this.eachWhere(query)},ObjectStore.prototype.each=function(options){return null==options&&(options={}),this.eachBy(void 0,options)},ObjectStore.prototype.eachBy=function(indexName,options){var q;return null==indexName&&(indexName=void 0),null==options&&(options={}),q=new Query,q.indexName=indexName,q.keyRange=keyRangeForOptions(options),q.direction=options.direction||defaultQueryOptions.direction,this.eachWhere(q)},ObjectStore.prototype.count=function(){var defer;return defer=this.defer(),defer.resolveWith(this.store.count()),defer.promise},ObjectStore.prototype.find=function(key){var defer,req;return defer=this.defer(),req=this.store.get(key),defer.rejectWith(req),req.onsuccess=function(_this){return function(e){return e.target.result?defer.resolve(e.target.result):defer.reject(""+_this.storeName+":"+key+" not found.")}}(this),defer.promise},ObjectStore.prototype.findBy=function(index,key){var defer;return defer=this.defer(),defer.resolveWith(this.store.index(index).get(key)),defer.promise},ObjectStore.prototype.query=function(){return new Query},ObjectStore}(),Query=function(){function Query(){this.indexName=void 0,this.keyRange=void 0,this.direction=cursorDirection.next}return Query.prototype.$lt=function(value){return this.keyRange=IDBKeyRange.upperBound(value,!0),this},Query.prototype.$gt=function(value){return this.keyRange=IDBKeyRange.lowerBound(value,!0),this},Query.prototype.$lte=function(value){return this.keyRange=IDBKeyRange.upperBound(value),this},Query.prototype.$gte=function(value){return this.keyRange=IDBKeyRange.lowerBound(value),this},Query.prototype.$eq=function(value){return this.keyRange=IDBKeyRange.only(value),this},Query.prototype.$between=function(low,hi,exLow,exHi){return null==exLow&&(exLow=!1),null==exHi&&(exHi=!1),this.keyRange=IDBKeyRange.bound(low,hi,exLow,exHi),this},Query.prototype.$desc=function(unique){return this.direction=unique?cursorDirection.prevunique:cursorDirection.prev,this},Query.prototype.$asc=function(unique){return this.direction=unique?cursorDirection.nextunique:cursorDirection.next,this},Query.prototype.$index=function(indexName){return this.indexName=indexName,this},Query}(),{openStore:function(storeName,callBack,mode){return null==mode&&(mode=dbMode.readwrite),openTransaction([storeName],mode).then(function(transaction){var results;return results=callBack(new ObjectStore(storeName,transaction)),appendResultsToPromise(transaction.promise,results)})},openStores:function(storeNames,callback,mode){return null==mode&&(mode=dbMode.readwrite),openTransaction(storeNames,mode).then(function(transaction){var objectStores,results,storeName;return objectStores=function(){var _i,_len,_results;for(_results=[],_i=0,_len=storeNames.length;_len>_i;_i++)storeName=storeNames[_i],_results.push(new ObjectStore(storeName,transaction));return _results}(),results=callback.apply(null,objectStores),appendResultsToPromise(transaction.promise,results)})},openAllStores:function(callback,mode){return null==mode&&(mode=dbMode.readwrite),openDatabase().then(function(){return function(){var objectStores,results,storeName,storeNames,transaction;return storeNames=Array.prototype.slice.apply(db.objectStoreNames),transaction=new Transaction(storeNames,mode),objectStores=function(){var _i,_len,_results;for(_results=[],_i=0,_len=storeNames.length;_len>_i;_i++)storeName=storeNames[_i],_results.push(new ObjectStore(storeName,transaction));return _results}(),results=callback.apply(null,objectStores),appendResultsToPromise(transaction.promise,results)}}(this))},closeDatabase:function(){return closeDatabase()},deleteDatabase:function(){return closeDatabase().then(function(){var defer;return defer=new DbQ,defer.resolveWith(indexedDB.deleteDatabase(dbName)),defer.promise})["finally"](function(){return console.debug("$indexedDB: "+dbName+" database deleted.")})},queryDirection:apiDirection,flush:function(){return allTransactions.length>0?$q.all(allTransactions):$q.when([])},databaseInfo:function(){return openDatabase().then(function(){var storeNames,transaction;return transaction=null,storeNames=Array.prototype.slice.apply(db.objectStoreNames),openTransaction(storeNames,dbMode.readonly).then(function(transaction){var store,storeName,stores;return stores=function(){var _i,_len,_results;for(_results=[],_i=0,_len=storeNames.length;_len>_i;_i++)storeName=storeNames[_i],store=transaction.objectStore(storeName),_results.push({name:storeName,keyPath:store.keyPath,autoIncrement:store.autoIncrement,indices:Array.prototype.slice.apply(store.indexNames)});return _results}(),transaction.promise.then(function(){return{name:db.name,version:db.version,objectStores:stores}})})})}}}]})}).call(this); 2 | //# sourceMappingURL=angular-indexed-db.min.js.map -------------------------------------------------------------------------------- /public/js/lib/xml2json.min.js: -------------------------------------------------------------------------------- 1 | function X2JS(v){var q="1.1.5";v=v||{};h();r();function h(){if(v.escapeMode===undefined){v.escapeMode=true;}v.attributePrefix=v.attributePrefix||"_";v.arrayAccessForm=v.arrayAccessForm||"none";v.emptyNodeForm=v.emptyNodeForm||"text";if(v.enableToStringFunc===undefined){v.enableToStringFunc=true;}v.arrayAccessFormPaths=v.arrayAccessFormPaths||[];if(v.skipEmptyTextNodesForObj===undefined){v.skipEmptyTextNodesForObj=true;}if(v.stripWhitespaces===undefined){v.stripWhitespaces=true;}v.datetimeAccessFormPaths=v.datetimeAccessFormPaths||[];}var g={ELEMENT_NODE:1,TEXT_NODE:3,CDATA_SECTION_NODE:4,COMMENT_NODE:8,DOCUMENT_NODE:9};function r(){function x(z){var y=String(z);if(y.length===1){y="0"+y;}return y;}if(typeof String.prototype.trim!=="function"){String.prototype.trim=function(){return this.replace(/^\s+|^\n+|(\s|\n)+$/g,"");};}if(typeof Date.prototype.toISOString!=="function"){Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+x(this.getUTCMonth()+1)+"-"+x(this.getUTCDate())+"T"+x(this.getUTCHours())+":"+x(this.getUTCMinutes())+":"+x(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1000).toFixed(3)).slice(2,5)+"Z";};}}function t(x){var y=x.localName;if(y==null){y=x.baseName;}if(y==null||y==""){y=x.nodeName;}return y;}function o(x){return x.prefix;}function p(x){if(typeof(x)=="string"){return x.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/");}else{return x;}}function j(x){return x.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(///g,"/");}function l(B,y,A){switch(v.arrayAccessForm){case"property":if(!(B[y] instanceof Array)){B[y+"_asArray"]=[B[y]];}else{B[y+"_asArray"]=B[y];}break;}if(!(B[y] instanceof Array)&&v.arrayAccessFormPaths.length>0){var x=0;for(;x1){B.setMilliseconds(z[1]);}if(A[6]&&A[7]){var y=A[6]*60+Number(A[7]);var x=/\d\d-\d\d:\d\d$/.test(C)?"-":"+";y=0+(x=="-"?-1*y:y);B.setMinutes(B.getMinutes()-y-B.getTimezoneOffset());}else{if(C.indexOf("Z",C.length-1)!==-1){B=new Date(Date.UTC(B.getFullYear(),B.getMonth(),B.getDate(),B.getHours(),B.getMinutes(),B.getSeconds(),B.getMilliseconds()));}}return B;}function n(A,y,z){if(v.datetimeAccessFormPaths.length>0){var B=z.split(".#")[0];var x=0;for(;x1&&F.__text!=null&&v.skipEmptyTextNodesForObj){if((v.stripWhitespaces&&F.__text=="")||(F.__text.trim()=="")){delete F.__text;}}}}delete F.__cnt;if(v.enableToStringFunc&&(F.__text!=null||F.__cdata!=null)){F.toString=function(){return(this.__text!=null?this.__text:"")+(this.__cdata!=null?this.__cdata:"");};}return F;}else{if(z.nodeType==g.TEXT_NODE||z.nodeType==g.CDATA_SECTION_NODE){return z.nodeValue;}}}}function m(E,B,D,y){var A="<"+((E!=null&&E.__prefix!=null)?(E.__prefix+":"):"")+B;if(D!=null){for(var C=0;C";}function s(y,x){return y.indexOf(x,y.length-x.length)!==-1;}function u(y,x){if((v.arrayAccessForm=="property"&&s(x.toString(),("_asArray")))||x.toString().indexOf(v.attributePrefix)==0||x.toString().indexOf("__")==0||(y[x] instanceof Function)){return true;}else{return false;}}function k(z){var y=0;if(z instanceof Object){for(var x in z){if(u(z,x)){continue;}y++;}}return y;}function b(z){var y=[];if(z instanceof Object){for(var x in z){if(x.toString().indexOf("__")==-1&&x.toString().indexOf(v.attributePrefix)==0){y.push(x);}}}return y;}function f(y){var x="";if(y.__cdata!=null){x+="";}if(y.__text!=null){if(v.escapeMode){x+=p(y.__text);}else{x+=y.__text;}}return x;}function c(y){var x="";if(y instanceof Object){x+=f(y);}else{if(y!=null){if(v.escapeMode){x+=p(y);}else{x+=y;}}}return x;}function e(z,B,A){var x="";if(z.length==0){x+=m(z,B,A,true);}else{for(var y=0;y0){for(var A in D){if(u(D,A)){continue;}var z=D[A];var C=b(z);if(z==null||z==undefined){x+=m(z,A,C,true);}else{if(z instanceof Object){if(z instanceof Array){x+=e(z,A,C);}else{if(z instanceof Date){x+=m(z,A,C,false);x+=z.toISOString();x+=i(z,A);}else{var y=k(z);if(y>0||z.__text!=null||z.__cdata!=null){x+=m(z,A,C,false);x+=d(z);x+=i(z,A);}else{x+=m(z,A,C,true);}}}}else{x+=m(z,A,C,false);x+=c(z);x+=i(z,A);}}}}x+=c(D);return x;}this.parseXmlString=function(z){var B=window.ActiveXObject||"ActiveXObject" in window;if(z===undefined){return null;}var A;if(window.DOMParser){var C=new window.DOMParser();var x=null;if(!B){try{x=C.parseFromString("INVALID","text/xml").childNodes[0].namespaceURI;}catch(y){x=null;}}try{A=C.parseFromString(z,"text/xml");if(x!=null&&A.getElementsByTagNameNS(x,"parsererror").length>0){A=null;}}catch(y){A=null;}}else{if(z.indexOf("")+2);}A=new ActiveXObject("Microsoft.XMLDOM");A.async="false";A.loadXML(z);}return A;};this.asArray=function(x){if(x instanceof Array){return x;}else{return[x];}};this.toXmlDateTime=function(x){if(x instanceof Date){return x.toISOString();}else{if(typeof(x)==="number"){return new Date(x).toISOString();}else{return null;}}};this.asDateTime=function(x){if(typeof(x)=="string"){return a(x);}else{return x;}};this.xml2json=function(x){return w(x);};this.xml_str2json=function(x){var y=this.parseXmlString(x);if(y!=null){return this.xml2json(y);}else{return null;}};this.json2xml_str=function(x){return d(x);};this.json2xml=function(y){var x=this.json2xml_str(y);return this.parseXmlString(x);};this.getVersion=function(){return q;};} -------------------------------------------------------------------------------- /public/js/tacmapServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 jdn 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | /* global Cesium, angular, stctl */ 18 | 19 | var databasename = "tacmapDb"; 20 | var storestructure = [ 21 | ['Resources', 'name', false, [['url', 'url', true], ['lastmod', 'lastmod', false], ['data', 'data', false]]], 22 | ['Missions', 'name', false, [['data', 'data', false]]] 23 | ]; 24 | Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3MTg2YTliMi05YWYzLTQ0MzgtYWRjNS01NjRlOGY4NjgwZGUiLCJpZCI6ODU3Nywic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU1MjMyNzk0Mn0.-bqHZmN7eaGyZPP_tSEklGadxL9WTBJLEfiQkd0fFkw'; 25 | var compression = false; 26 | var viewer = new Cesium.Viewer('cesiumContainer', { 27 | animation: false, 28 | timeline: false, 29 | infoBox: true, 30 | selectionIndicator: true, 31 | baseLayerPicker: true, 32 | navigationHelpButton: false, 33 | navigationInstructionsInitiallyVisible: false, 34 | 35 | imageryProvider: new Cesium.BingMapsImageryProvider({ 36 | url: '//dev.virtualearth.net', 37 | key: 'AtO-6nT2HLG-BF4IvAbGvEppiYqeW9W4KSemE-7wVH_9GgWnThseKQdwe4-xj-S0', 38 | mapStyle: Cesium.BingMapsStyle.AERIAL_WITH_LABELS 39 | }), 40 | /*imageryProvider: new Cesium.GoogleEarthImageryProvider({ 41 | url: '//earth.localdomain', 42 | channel: 1008 43 | }),*/ 44 | // imageryProvider: new Cesium.TileMapServiceImageryProvider({ 45 | // url: 'Cesium/Assets/Textures/NaturalEarthII' 46 | // }), 47 | // OpenStreetMap tile provider 48 | // imageryProvider: new Cesium.OpenStreetMapImageryProvider({ 49 | // url: '/tiles' 50 | // }), 51 | homeButton: false, 52 | geocoder: false 53 | }); 54 | var scene = viewer.scene; 55 | var TacMapServer = angular.module('TacMapServer', ['indexedDB']); 56 | 57 | TacMapServer.config(function ($indexedDBProvider) { 58 | $indexedDBProvider.connection(databasename).upgradeDatabase(1, function (event, db, tx) { 59 | console.log("initDb"); 60 | for (i = 0; i < storestructure.length; i++) { 61 | //console.log("add store " + storestructure[i][0] + " key:" + storestructure[i][1] + " autoinc:" + storestructure[i][2]); 62 | var objectStore = db.createObjectStore(storestructure[i][0], { 63 | keyPath: storestructure[i][1], autoIncrement: storestructure[i][2] 64 | }); 65 | var indices = storestructure[i][3]; 66 | for (j = 0; j < indices.length; j++) { 67 | //console.log("add index " + indices[j][0] + " ref:" + indices[j][1] + " unique:" + indices[j][2]); 68 | objectStore.createIndex(indices[j][0], indices[j][1], { 69 | unique: indices[j][2] 70 | }); 71 | } 72 | } 73 | }); 74 | }); 75 | 76 | -------------------------------------------------------------------------------- /public/js/tacmapUnit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 jdn 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | /* global Cesium, angular, vwctl */ 18 | 19 | var databasename = "tacmapUnitDb"; 20 | var storestructure = [ 21 | ['Resources', 'name', false, [['url', 'url', true], ['lastmod', 'lastmod', false], ['data', 'data', false]]], 22 | ['Missions', 'name', false, [['data', 'data', false]]] 23 | ]; 24 | Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3MTg2YTliMi05YWYzLTQ0MzgtYWRjNS01NjRlOGY4NjgwZGUiLCJpZCI6ODU3Nywic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU1MjMyNzk0Mn0.-bqHZmN7eaGyZPP_tSEklGadxL9WTBJLEfiQkd0fFkw'; 25 | var compression = false; 26 | var viewer = new Cesium.Viewer('cesiumContainer', { 27 | animation: false, 28 | timeline: false, 29 | infoBox: true, 30 | selectionIndicator: true, 31 | baseLayerPicker: true, 32 | navigationHelpButton: false, 33 | navigationInstructionsInitiallyVisible: false, 34 | // imageryProvider: new Cesium.ArcGisMapServerImageryProvider({ 35 | // url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' 36 | // }), 37 | // imageryProvider: new Cesium.ArcGisMapServerImageryProvider({ 38 | // url: '//services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' 39 | // }), 40 | imageryProvider: new Cesium.BingMapsImageryProvider({ 41 | url: '//dev.virtualearth.net', 42 | key: 'AtO-6nT2HLG-BF4IvAbGvEppiYqeW9W4KSemE-7wVH_9GgWnThseKQdwe4-xj-S0', 43 | mapStyle: Cesium.BingMapsStyle.AERIAL_WITH_LABELS 44 | }), 45 | /*imageryProvider: new Cesium.GoogleEarthImageryProvider({ 46 | url: '//earth.localdomain', 47 | channel: 1008 48 | }),*/ 49 | // imageryProvider: new Cesium.TileMapServiceImageryProvider({ 50 | // url: 'Cesium/Assets/Textures/NaturalEarthII' 51 | // }), 52 | // OpenStreetMap tile provider 53 | // imageryProvider: new Cesium.OpenStreetMapImageryProvider({ 54 | // url: '/tiles' 55 | // }), 56 | homeButton: false, 57 | geocoder: false 58 | }); 59 | var scene = viewer.scene; 60 | var xj = new X2JS(); 61 | var TacMapUnit = angular.module('TacMapUnit', ['indexedDB']); 62 | TacMapUnit.config(function ($indexedDBProvider) { 63 | $indexedDBProvider.connection(databasename).upgradeDatabase(1, function (event, db, tx) { 64 | console.log("initDb"); 65 | for (i = 0; i < storestructure.length; i++) { 66 | //console.log("add store " + storestructure[i][0] + " key:" + storestructure[i][1] + " autoinc:" + storestructure[i][2]); 67 | var objectStore = db.createObjectStore(storestructure[i][0], { 68 | keyPath: storestructure[i][1], autoIncrement: storestructure[i][2] 69 | }); 70 | var indices = storestructure[i][3]; 71 | for (j = 0; j < indices.length; j++) { 72 | //console.log("add index " + indices[j][0] + " ref:" + indices[j][1] + " unique:" + indices[j][2]); 73 | objectStore.createIndex(indices[j][0], indices[j][1], { 74 | unique: indices[j][2] 75 | }); 76 | } 77 | } 78 | }); 79 | }); -------------------------------------------------------------------------------- /public/js/tmSrvrControllers.js: -------------------------------------------------------------------------------- 1 | /* global resources */ 2 | 3 | // ***** SERVER CONTROLLERS ******// 4 | TacMapServer.controller('storeCtl', function ($indexedDB, $scope, $http, GeoService, MsgService, DlgBx) { 5 | var stctl = this; 6 | console.log("storeCtl"); 7 | stctl.xj = new X2JS(); 8 | var dB = $indexedDB; 9 | var ellipsoid = scene.globe.ellipsoid; 10 | stctl.unitselected = null; 11 | stctl.unitselectedid = null; 12 | stctl.currmission = null; 13 | stctl.editchecked = false; 14 | stctl.editlocchecked = false; 15 | stctl.editrptchecked = false; 16 | stctl.rptto = []; 17 | stctl.import = false; 18 | // 19 | stctl.entities = []; 20 | stctl.units = []; 21 | stctl.networks = []; 22 | stctl.mission = []; 23 | stctl.missionlist = []; 24 | stctl.waypoints = []; 25 | stctl.loc = []; 26 | stctl.showWP = 0; 27 | $scope.selmission = { id: 0, name: 'Default' }; 28 | $scope.netselected = []; 29 | stctl.speedsel = []; 30 | // 31 | stctl.lftClickHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); 32 | stctl.lftClickHandler.setInputAction(function (mouse) { 33 | var pickedObject = scene.pick(mouse.position); 34 | if (Cesium.defined(pickedObject) && pickedObject.id.position !== undefined && pickedObject.id.billboard) { 35 | stctl.selectUnit(pickedObject.id); 36 | } else { 37 | stctl.loc = []; 38 | stctl.unitselected = null; 39 | $scope.$apply(); 40 | } 41 | }, 42 | Cesium.ScreenSpaceEventType.LEFT_CLICK); 43 | stctl.rtClickHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); 44 | stctl.rtClickHandler.setInputAction(function (mouse) { 45 | //console.log("edit: " + stctl.editchecked); 46 | //console.log("speed: " + $scope.speedsel._value); 47 | var cartesian = viewer.camera.pickEllipsoid(mouse.position, ellipsoid); 48 | if (stctl.editchecked && cartesian && stctl.unitselected !== null) { 49 | //console.log("unitselected: " + stctl.unitselected._id); 50 | var cartographic = ellipsoid.cartesianToCartographic(cartesian); 51 | stctl.addWaypoint(stctl.loc, Cesium.Math.toDegrees(cartographic.latitude), Cesium.Math.toDegrees(cartographic.longitude), stctl.speedsel._value); 52 | } 53 | if (stctl.editlocchecked && cartesian && stctl.unitselected !== null) { 54 | var cartographic = ellipsoid.cartesianToCartographic(cartesian); 55 | stctl.setLocation(stctl.unitselected, Cesium.Math.toDegrees(cartographic.latitude), Cesium.Math.toDegrees(cartographic.longitude)); 56 | } 57 | }, 58 | Cesium.ScreenSpaceEventType.RIGHT_CLICK); 59 | 60 | stctl.selectUnit = function (u, zoomto) { 61 | //console.log($scope.selmission.id); 62 | //console.log(GeoService.sdatasources[$scope.selmission.name]); 63 | stctl.unitselected = GeoService.sdatasources[$scope.selmission.name].entities.getById(u._id); 64 | stctl.unitselectedid = stctl.unitselected._id; 65 | stctl.loc = stctl.getLoc(stctl.unitselected); 66 | if (zoomto) { 67 | GeoService.sdatasources[$scope.selmission.name].selectedEntity = stctl.unitselected; 68 | viewer.selectedEntity = stctl.unitselected; 69 | viewer.camera.flyTo({ 70 | destination: Cesium.Cartesian3.fromDegrees(stctl.loc[1], stctl.loc[0], 10000.0), 71 | duration: 1 72 | }); 73 | } else { 74 | $scope.$apply(); 75 | } 76 | }; 77 | stctl.getLoc = function (entity) { 78 | var cartesian = entity.position.getValue(); 79 | var cartographic = ellipsoid.cartesianToCartographic(cartesian); 80 | var latitudeString = Cesium.Math.toDegrees(cartographic.latitude); 81 | var longitudeString = Cesium.Math.toDegrees(cartographic.longitude); 82 | entity.description = "Location: " + latitudeString + ", " + longitudeString; 83 | return ([latitudeString, longitudeString]); 84 | }; 85 | stctl.setLocation = function (entity, lat, lng) { 86 | GeoService.sdatasources[$scope.selmission.name].entities.getById(entity._id).position = Cesium.Cartesian3.fromDegrees(lng, lat); 87 | stctl.removeAllWp(); 88 | stctl.updateDb(entity._id, '_location', lat + "," + lng); 89 | stctl.selectUnit(entity); 90 | }; 91 | // 92 | stctl.addWaypoint = function (startpt, lat, lng, speed) { 93 | //console.log("Add Waypoint " + stctl.unitselected._id + " " + lat + " " + lng + " " + speed); 94 | if (GeoService.waypoints[stctl.unitselected._id]) { 95 | stctl.waypoints[stctl.unitselected._id] = GeoService.waypoints[stctl.unitselected._id]; 96 | } 97 | if (!stctl.waypoints[stctl.unitselected._id]) { 98 | stctl.waypoints[stctl.unitselected._id] = []; 99 | stctl.waypoints[stctl.unitselected._id].push([startpt[0], startpt[1], speed]); 100 | } 101 | if (stctl.waypoints[stctl.unitselected._id].length === 0) { 102 | stctl.waypoints[stctl.unitselected._id].push([startpt[0], startpt[1], speed]); 103 | } 104 | stctl.waypoints[stctl.unitselected._id].push([lat, lng, speed]); 105 | GeoService.waypoints[stctl.unitselected._id] = stctl.waypoints[stctl.unitselected._id]; 106 | var obj = stctl.waypoints[stctl.unitselected._id]; 107 | var len = obj.length; 108 | //console.log("len: " + len); 109 | if (len > 1) { 110 | var arr = [obj[len - 2][1], obj[len - 2][0], obj[len - 1][1], obj[len - 1][0]]; 111 | GeoService.wpdatasources[$scope.selmission.name].entities.add({ 112 | id: stctl.unitselected._id + 'WP' + stctl.waypoints[stctl.unitselected._id].length, 113 | polyline: { 114 | positions: Cesium.Cartesian3.fromDegreesArray(arr), 115 | width: 1, 116 | material: Cesium.Color.LIGHTYELLOW 117 | } 118 | }); 119 | stctl.updateDb(stctl.unitselected._id, "waypoints", stctl.waypoints[stctl.unitselected._id]); 120 | } 121 | }; 122 | stctl.removeLastWp = function () { 123 | stctl.waypoints[stctl.unitselected._id] = GeoService.waypoints[stctl.unitselected._id]; 124 | if (stctl.waypoints[stctl.unitselected._id]) { 125 | if (stctl.unitselected && stctl.waypoints[stctl.unitselected._id].length > 1) { 126 | console.log("Remove Waypoint " + stctl.unitselected._id + 'WP' + stctl.waypoints[stctl.unitselected._id].length); 127 | GeoService.wpdatasources[$scope.selmission.name].entities.removeById(stctl.unitselected._id + 'WP' + stctl.waypoints[stctl.unitselected._id].length); 128 | stctl.waypoints[stctl.unitselected._id].splice(-1, 1); 129 | GeoService.waypoints[stctl.unitselected._id] = stctl.waypoints[stctl.unitselected._id]; 130 | stctl.updateDb(stctl.unitselected._id, "waypoints", stctl.waypoints[stctl.unitselected._id]); 131 | } 132 | } 133 | }; 134 | stctl.removeAllWp = function () { 135 | if (GeoService.waypoints[stctl.unitselected._id]) { 136 | stctl.waypoints[stctl.unitselected._id] = GeoService.waypoints[stctl.unitselected._id]; 137 | } 138 | if (stctl.waypoints[stctl.unitselected._id]) { 139 | var len = stctl.waypoints[stctl.unitselected._id].length; 140 | for (ln = len; ln > 0; ln--) { 141 | GeoService.wpdatasources[$scope.selmission.name].entities.removeById(stctl.unitselected._id + 'WP' + ln); 142 | } 143 | } 144 | stctl.waypoints[stctl.unitselected._id] = []; 145 | GeoService.waypoints[stctl.unitselected._id] = []; 146 | stctl.updateDb(stctl.unitselected._id, "waypoints", stctl.waypoints[stctl.unitselected._id]); 147 | }; 148 | // 149 | stctl.saveMission = function (currentmission) { 150 | console.log("saveMission"); 151 | DlgBx.prompt("Enter Save As Name or Overwrite", currentmission.value).then(function (newname) { 152 | var overwrite = null; 153 | var overwriteid = null; 154 | for (n = 0; n < stctl.missionlist.length - 1; n++) { 155 | if (newname === stctl.missionlist[n].value) { 156 | overwrite = stctl.missionlist[n].value; 157 | overwriteid = stctl.missionlist[n].value; 158 | } 159 | } 160 | if (overwrite !== null) { 161 | DlgBx.confirm("This Action will Overwrite Mission", overwrite).then(function (yes) { 162 | if (yes) { 163 | console.log("Save " + overwrite); 164 | stctl.overwriteMission(overwrite); 165 | stctl.currmission = currentmission; 166 | stctl.loadMission({ id: overwriteid, name: overwrite }); 167 | } 168 | }); 169 | } else { 170 | console.log("Save " + newname); 171 | stctl.copyMission($scope.selmission.name, newname); 172 | stctl.missionlist.push({ 173 | id: stctl.missionlist.length - 1, name: newname 174 | }); 175 | stctl.currmission = currentmission; 176 | stctl.loadMission(stctl.missionlist[stctl.missionlist.length - 1]); 177 | } 178 | }); 179 | }; 180 | stctl.loadMission = function (nextmission) { 181 | console.log("loadMission " + nextmission.name); 182 | //console.log("Current Mission:" + stctl.currmission.value); 183 | $scope.netselected = []; 184 | viewer.dataSources.remove(GeoService.sdatasources[$scope.selmission.name]); 185 | viewer.dataSources.remove(GeoService.wpdatasources[$scope.selmission.name]); 186 | dB.openStore("Missions", function (store) { 187 | store.find(nextmission.name).then(function (sc) { 188 | stctl.entities = sc.data.Mission.Entities.Entity; 189 | stctl.networks = sc.data.Mission.Networks.Network; 190 | GeoService.initGeodesy(nextmission.name, sc.data, $scope); 191 | stctl.currmission = nextmission; 192 | MsgService.setMission(nextmission.name, sc.data); 193 | GeoService.joinNetworks(stctl.entities, stctl.networks, MsgService, $scope); 194 | }); 195 | }); 196 | $scope.selmission = nextmission; 197 | }; 198 | // 199 | stctl.clearDb = function () { 200 | console.log("Clear DB"); 201 | DlgBx.confirm("Confirm Deletion of Local Data").then(function () { 202 | viewer.dataSources.remove(GeoService.sdatasources[$scope.selmission.name]); 203 | viewer.dataSources.remove(GeoService.wpdatasources[$scope.selmission.name]); 204 | dB.openStore('Resources', function (store) { 205 | store.clear(); 206 | }); 207 | dB.openStore('Mission', function (store) { 208 | store.clear(); 209 | }); 210 | }); 211 | }; 212 | stctl.exportMission = function () { 213 | console.log("exportMission"); 214 | DlgBx.prompt("Enter Export Save As Name:", $scope.selmission.name).then(function (newname) { 215 | if (newname === 'Default') { 216 | DlgBx.alert("You Can't' Overwrite the Default Mission"); 217 | } else { 218 | var overwrite = null; 219 | for (n = 0; n < stctl.missionlist.length; n++) { 220 | if (newname === stctl.missionlist[n].mission) { 221 | overwrite = stctl.missionlist[n].mission; 222 | } 223 | } 224 | if (overwrite !== null) { 225 | DlgBx.confirm("This Action will Overwrite Mission", overwrite).then(function (yes) { 226 | if (yes) { 227 | console.log("Export " + overwrite); 228 | dB.openStore("Missions", function (store) { 229 | store.find(overwrite).then(function (scen) { 230 | var mission = scen.data; 231 | //console.log(mission); 232 | $http.put("/json/" + overwrite.replace(' ', '') + '.json', mission); 233 | }); 234 | }); 235 | } 236 | }); 237 | } else { 238 | console.log("Export " + newname); 239 | dB.openStore("Missions", function (store) { 240 | store.find($scope.selmission.name).then(function (scen) { 241 | var mission = scen.data; 242 | $http.post("/json/" + newname.replace(' ', '') + '.json', mission) 243 | .success(function () { 244 | console.log("Saved " + newname + " to /json/" + newname.replace(' ', '') + ".json"); 245 | stctl.missionlist.push({ 246 | id: stctl.missionlist.length - 1, name: newname.replace(' ', '') + ".json", url: "/json/" + newname.replace(' ', '') + ".json" 247 | }); 248 | dB.openStore('Resources', function (store) { 249 | store.upsert({ 250 | name: "missions.json", url: "/json/missions.json", data: stctl.missionlist 251 | }).then(function () { 252 | store.find("missions.json").then(function (st) { 253 | $http.put('/json/missions.json', st.data); 254 | }); 255 | }); 256 | }); 257 | }); 258 | }); 259 | }); 260 | } 261 | } 262 | stctl.import = false; 263 | }); 264 | }; 265 | stctl.importMission = function () { 266 | stctl.import = true; 267 | }; 268 | stctl.getFile = function (savedmission) { 269 | console.log("Get File: " + savedmission.name + ", " + savedmission.url); 270 | $http.get(savedmission.url).success(function (sdata) { 271 | DlgBx.prompt("Enter Save As Name or Overwrite", savedmission.mission).then(function (newname) { 272 | if (newname === "Default") { 273 | DlgBx.alert("You Can't' Overwrite the Default Mission"); 274 | } else { 275 | var overwrite = null; 276 | for (i = 0; i < stctl.missionlist.length; i++) { 277 | if (newname === stctl.missionlist[i].value) { 278 | overwrite = stctl.missionlist[i].value; 279 | console.log(overwrite); 280 | overwriteid = stctl.missionlist[i].value; 281 | break; 282 | } 283 | } 284 | if (overwrite !== null) { 285 | console.log(overwrite); 286 | DlgBx.confirm("This Action will Overwrite Mission " + overwrite).then(function (yes) { 287 | if (yes) { 288 | stctl.mission = sdata; 289 | stctl.overwriteMission(overwrite); 290 | } 291 | }); 292 | } else { 293 | console.log("Save " + newname); 294 | stctl.mission = sdata; 295 | dB.openStore("Missions", function (store) { 296 | store.insert({ name: newname, data: sdata }).then(function () { 297 | stctl.missionlist.push({ 298 | id: stctl.missionlist.length - 1, name: newname 299 | }); 300 | stctl.currmission = { id: stctl.missionlist.length - 1, name: newname }; 301 | stctl.loadMission(savedmission); 302 | }); 303 | }); 304 | } 305 | } 306 | }); 307 | }); 308 | }; 309 | // 310 | stctl.updateDb = function (entityId, fieldname, value) { 311 | dB.openStore("Missions", function (store) { 312 | store.find($scope.selmission.name).then(function (mission) { 313 | stctl.mission = mission.data; 314 | for (i = 0; i < stctl.mission.Mission.Entities.Entity.length; i++) { 315 | if (stctl.mission.Mission.Entities.Entity[i]._id === entityId) { 316 | stctl.mission.Mission.Entities.Entity[i][fieldname] = value; 317 | } 318 | } 319 | }).then(function () { 320 | store.upsert({ name: $scope.selmission.name, data: stctl.mission }); 321 | }); 322 | }); 323 | }; 324 | stctl.updateMission = function () { 325 | dB.openStore("Missions", function (store) { 326 | store.upsert({ name: $scope.selmission.name, data: stctl.mission }); 327 | }); 328 | }; 329 | stctl.copyMission = function (currentmission, newmissionid) { 330 | dB.openStore("Missions", function (store) { 331 | store.find(currentmission).then(function (mission) { 332 | store.insert({ name: newmissionid, data: mission.data }); 333 | }); 334 | }); 335 | }; 336 | stctl.overwriteMission = function (missionid) { 337 | console.log("overwriteMission: " + missionid); 338 | dB.openStore("Missions", function (store) { 339 | store.find(missionid).then(function () { 340 | store["delete"](missionid).then(function () { 341 | store.insert({ name: missionid, data: stctl.mission }); 342 | }); 343 | }); 344 | }); 345 | }; 346 | stctl.deleteMission = function (currentmission) { 347 | if ($scope.selmission.id === 0) { 348 | DlgBx.alert("Can't delete Default Mission"); 349 | } else { 350 | DlgBx.confirm("Confirm deletion of Mission: " + currentmission.value).then(function (yes) { 351 | console.log("Confirm response: " + $scope.selmission.id); 352 | if (yes && $scope.selmission.id !== 0) { 353 | console.log("Delete from Idb: " + currentmission.value); 354 | dB.openStore("Missions", function (store) { 355 | store["delete"](currentmission.value); 356 | }); 357 | var na = []; 358 | for (i = 0; i < stctl.missionlist.length; i++) { 359 | if (stctl.missionlist[i].value !== currentmission.value) { 360 | na.push(stctl.missionlist[i]); 361 | } 362 | } 363 | stctl.missionlist = na; 364 | stctl.loadMission(stctl.missionlist[na.length - 1]); 365 | } else { 366 | 367 | } 368 | }); 369 | } 370 | }; 371 | // 372 | stctl.addFile = function (mission, filename, data) { 373 | $http.post("/json/" + filename, data) 374 | .success(function () { 375 | console.log("Saved " + mission + " to /json/" + filename + ".json"); 376 | stctl.missionlist.push({ 377 | id: stctl.missionlist.length - 1, name: filename, url: "/json/" + filename 378 | }); 379 | dB.openStore('Resources', function (store) { 380 | store.upsert({ 381 | name: "missions.json", url: resources[1], data: stctl.missionlist 382 | }).then(function () { 383 | $http.post("/json/missions.json", stctl.missionlist).success( 384 | function () { 385 | console.log("Updated File List"); 386 | }); 387 | }); 388 | }); 389 | }); 390 | }; 391 | stctl.overwriteFile = function (mission, filename, data) { 392 | $http.post("/json/" + filename, data) 393 | .success(function () { 394 | console.log("Saved " + mission + " to /json/" + filename + ".json"); 395 | }); 396 | }; 397 | // 398 | stctl.toggleWaypoints = function () { 399 | console.log(stctl.showWP); 400 | if (stctl.showWP) { 401 | GeoService.showAllWP(); 402 | } else { 403 | GeoService.hideAllWP(); 404 | } 405 | }; 406 | stctl.showUnit = function (unit) { 407 | if ($scope.netselected[unit._network]) { 408 | return $scope.netselected[unit._network].show; 409 | } 410 | }; 411 | stctl.selectNetwork = function (net) { 412 | console.log("selectNetwork " + net._name); 413 | stctl.entities = GeoService.entities; 414 | if ($scope.netselected[net._name]) { 415 | if ($scope.netselected[net._name].show === true) { 416 | $scope.netselected[net._name].show = false; 417 | MsgService.leaveNet(net._name); 418 | } else { 419 | $scope.netselected[net._name].show = true; 420 | $scope.netselected[net._name].network = net._name; 421 | MsgService.joinNet(net._name); 422 | } 423 | GeoService.setNetViz(stctl.entities, $scope.netselected); 424 | return $scope.netselected[net._name].show; 425 | } else { 426 | $scope.netselected[net._name] = []; 427 | $scope.netselected[net._name].show = true; 428 | $scope.netselected[net._name].network = net._name; 429 | GeoService.setNetViz(stctl.entities, $scope.netselected); 430 | MsgService.joinNet(net._name); 431 | return $scope.netselected[GeoService.entities, net._name].show; 432 | } 433 | }; 434 | stctl.netSelected = function (net) { 435 | if ($scope.netselected[net._name]) { 436 | return $scope.netselected[net._name].show; 437 | } 438 | }; 439 | // 440 | stctl.sortByKey = function (array, key) { 441 | return array.sort(function (a, b) { 442 | var x = a[key]; 443 | var y = b[key]; 444 | return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 445 | }); 446 | }; 447 | // 448 | stctl.syncResource = function (msnid, $http, url, dB, stctl, GeoService) { 449 | $http.get(url).then(function (result) { 450 | var mod = result.headers()['last-modified']; 451 | var filename = url.substring(url.lastIndexOf('/') + 1); 452 | var jdata = stctl.xj.xml_str2json(result.data); 453 | var mname = jdata.Mission._name; 454 | var jname = mname.replace(' ', '').toLowerCase(); 455 | stctl.missionlist.push({ id: msnid, name: mname, url: 'json/' + jname + '.json' }); 456 | $http.post("/json/missions.json", angular.toJson(stctl.sortByKey(stctl.missionlist, 'id'))); 457 | if (filename === 'DefaultMission.xml') { 458 | $http.get('/json/defaultmission.json').then(function (result) { 459 | var jdata = result.data; 460 | dB.openStore('Missions', function (mstore) { 461 | mstore.upsert({ name: mname, url: 'json/' + jname + '.json', data: jdata }).then(function () { 462 | console.log('init geo'); 463 | stctl.mission = jdata; 464 | stctl.networks = jdata.Mission.Networks.Network; 465 | stctl.speeds = jdata.Mission.Speeds.Speed; 466 | stctl.speedsel = stctl.speeds[0]; 467 | stctl.entities = jdata.Mission.Entities.Entity; 468 | GeoService.initGeodesy(jdata.Mission._name, jdata); 469 | $scope.selmission.name = jdata.Mission._name; 470 | stctl.loadMission({ id: 0, name: "Default" }); 471 | }); 472 | }); 473 | }); 474 | } else { 475 | dB.openStore('Missions', function (mstore) { 476 | mstore.upsert({ name: mname, url: 'json/' + jname + '.json', data: jdata }).then(function () { 477 | dB.openStore('Resources', function (store) { 478 | store.getAllKeys().then(function (keys) { 479 | if (keys.indexOf(filename) === -1) { 480 | store.upsert({ name: filename, url: url, lastmod: mod, data: jdata }); 481 | } else { 482 | store.find(filename).then(function (dbrec) { 483 | if (dbrec.lastmod !== mod) { 484 | console.log('upsert ' + filename); 485 | store.upsert({ name: filename, url: url, lastmod: mod, data: jdata }); 486 | } 487 | }); 488 | } 489 | stctl.loadMission({ id: 0, name: "Default" }); 490 | }); 491 | }); 492 | }); 493 | }); 494 | } 495 | }) 496 | }; 497 | // 498 | MsgService.socket.on('connection', function (data) { 499 | MsgService.serverid = data.socketid; 500 | MsgService.connectServer(data, $scope.selmission.name, stctl.mission); 501 | }); 502 | MsgService.socket.on('unit connected', function (data) { 503 | console.log("Unit connected " + data.id); 504 | MsgService.setMission($scope.selmission.name, stctl.mission); 505 | //msgctl.messages.push({text: "Unit " + data.socketid + " connected"}); 506 | 507 | }); 508 | MsgService.socket.on('init server', function (data) { 509 | console.log('init server: ' + data.missionid); 510 | $scope.selmission.name = data.scenarioname; 511 | $http.get('xml/missions.xml').then(function (resdata, status, headers) { 512 | var msns = stctl.xj.xml_str2json(resdata.data); 513 | for (i = 0; i < msns.Missions.Mission.length; i++) { 514 | var u = msns.Missions.Mission[i]._url; 515 | var n = msns.Missions.Mission[i]._name; 516 | if (u.substring(u.indexOf('.')) === '.xml') { 517 | stctl.syncResource(msns.Missions.Mission[i]._id, $http, msns.Missions.Mission[i]._url, dB, stctl, GeoService); 518 | } else { 519 | stctl.missionlist.push({ id: msns.Missions.Mission[i]._id, name: n, url: u }); 520 | $http.get(u).success(function (jsondata, status, headers) { 521 | dB.openStore('Missions', function (mstore) { 522 | console.log(n); 523 | mstore.upsert({ name: n, url: u, data: jsondata }); 524 | $http.post("/json/missions.json", angular.toJson(stctl.sortByKey(stctl.missionlist, 'id'))); 525 | }); 526 | }); 527 | } 528 | } 529 | }); 530 | }); 531 | }); 532 | TacMapServer.controller('messageCtl', function ($indexedDB, $scope, $interval, GeoService, MsgService) { 533 | var msgctl = this; 534 | msgctl.dB = $indexedDB; 535 | msgctl.messages = []; 536 | //Move in increments of this many meters. 537 | msgctl.time = 0; //seconds 538 | msgctl.interval = 10000; //Rpt every 10 seconds 539 | msgctl.units = []; 540 | msgctl.movecount = 0; 541 | msgctl.moveleg = 10; //meters 542 | msgctl.running = false; 543 | msgctl.resetMission = function () { 544 | console.log("resetMission"); 545 | msgctl.movecount = 0; 546 | msgctl.time = 0; 547 | msgctl.running = false; 548 | MsgService.socket.emit("mission stopped"); 549 | MsgService.socket.emit("mission time", { time: msgctl.time }); 550 | $interval.cancel(msgctl.playMission); 551 | for (m = 0; m < msgctl.units.length; m++) { 552 | var mv = GeoService.movementsegments[msgctl.units[m]][0]; 553 | if (mv) { 554 | GeoService.sdatasources[$scope.selmission.name].entities.getById(msgctl.units[m]).position = Cesium.Cartesian3.fromDegrees(mv.lon, mv.lat); 555 | } 556 | } 557 | }; 558 | msgctl.runMission = function () { 559 | console.log("playMission"); 560 | msgctl.running = true; 561 | MsgService.socket.emit("mission running"); 562 | for (var key in GeoService.movementsegments) { 563 | if (GeoService.movementsegments.hasOwnProperty(key)) { //to be safe 564 | msgctl.units.push(key); 565 | } 566 | } 567 | for (j = 0; j < msgctl.units.length; j++) { 568 | console.log(msgctl.units[j] + ": " + GeoService.movementsegments[msgctl.units[j]].length + " movements"); 569 | } 570 | msgctl.playMission = $interval(msgctl.moveUnits, msgctl.interval); 571 | }; 572 | msgctl.pauseMission = function () { 573 | console.log("pauseMission"); 574 | msgctl.running = false; 575 | MsgService.socket.emit("mission stopped"); 576 | $interval.cancel(msgctl.playMission); 577 | }; //move units to location at specified time interval 578 | msgctl.moveUnits = function () { 579 | for (m = 0; m < msgctl.units.length; m++) { 580 | var mv = GeoService.movementsegments[msgctl.units[m]][msgctl.movecount]; 581 | if (mv) { 582 | var unitid = msgctl.units[m]; 583 | var unit = GeoService.sdatasources[$scope.selmission.name].entities.getById(unitid) 584 | //console.log(unit); 585 | unit_location = mv.lat + "," + mv.lon; 586 | unit._rpttime = new Date().getTime(); 587 | unit.position = Cesium.Cartesian3.fromDegrees(mv.lon, mv.lat); 588 | msgctl.sendReport({ unit: unit._id, time: unit._rpttime, position: [mv.lat, mv.lon] }); 589 | } 590 | } 591 | msgctl.movecount++; 592 | msgctl.time = (msgctl.movecount * msgctl.interval) / 1000; 593 | MsgService.socket.emit("mission time", { time: msgctl.time }); 594 | }; 595 | msgctl.sendReport = function (msgobj) { 596 | //default ui 597 | MsgService.sendMessage(msgobj, msgobj.network); 598 | }; 599 | msgctl.moveUnit = function (uid, sentto, net, lat, lon) { 600 | console.log("moveUnit: " + uid); 601 | GeoService.sdatasources[$scope.selmission.name].entities.getById(uid).position = Cesium.Cartesian3.fromDegrees(lon, lat); 602 | msgctl.sendReport({ unit: uid, to: sentto, time: new Date(), position: [lat, lon], network: net }); 603 | }; 604 | MsgService.socket.on('error', console.error.bind(console)); 605 | MsgService.socket.on('message', console.log.bind(console)); 606 | MsgService.socket.on('msg sent', function (data) { 607 | msgctl.messages.push({ text: "POSREP " + data.net + " " + data.message.unit }); 608 | GeoService.sdatasources[$scope.selmission.name].entities.getById(data.message.unit).position = Cesium.Cartesian3.fromDegrees(data.message.position[1], data.message.position[0]); 609 | }); 610 | MsgService.socket.on('unit disconnected', function (data) { 611 | console.log("Unit disconnected " + data.socketid); 612 | msgctl.messages.push({ text: "Unit " + data.socketid + " disconnected" }); 613 | }); 614 | MsgService.socket.on('unit joined', function (data) { 615 | //console.log('Unit ' + data.unitid + ' Joined Network: ' + data.netname); 616 | msgctl.messages.push({ text: 'Unit ' + data.unitid + ' Joined Network: ' + data.netname }); 617 | }); 618 | MsgService.socket.on('unit left', function (data) { 619 | console.log('Unit ' + data.unitid + ' Left Network: ' + data.netname); 620 | msgctl.messages.push({ text: 'Unit ' + data.unitid + ' Left Network: ' + data.netname }); 621 | }); 622 | MsgService.socket.on('server joined', function (data) { 623 | //console.log('Joined Network: ' + data.netname); 624 | msgctl.messages.push({ text: 'Joined Network: ' + data.netname }); 625 | }); 626 | MsgService.socket.on('server left', function (data) { 627 | //console.log('Left Network: ' + data.netname); 628 | msgctl.messages.push({ text: 'Left Network: ' + data.netname }); 629 | }); 630 | /* MsgService.socket.on("start mission", function () { 631 | console.log("start mission"); 632 | msgctl.running = true; 633 | $scope.$apply(); 634 | }); */ 635 | MsgService.socket.on("stop mission", function () { 636 | msgctl.running = false; 637 | $scope.$apply(); 638 | }); 639 | MsgService.socket.on("set time", function (data) { 640 | msgctl.time = data.time; 641 | $scope.$apply(); 642 | }); 643 | // 644 | msgctl.timeCalc = function (timeobj) { 645 | var day = timeobj.Day; 646 | var hr = timeobj.HourTime; 647 | var min = timeobj.MinuteTime; 648 | var sec = timeobj.SecondTime; 649 | var month = timeobj.MonthNumeric; 650 | var yr = timeobj.Year4Digit; 651 | var d = new Date(yr, month, day, hr, min, sec); 652 | return d.getTime(); 653 | }; 654 | }); 655 | TacMapServer.controller('menuCtrl', function ($scope) { 656 | //initiate an array to hold all active tabs 657 | $scope.activeTabs = []; 658 | //check if the tab is active 659 | $scope.isOpenTab = function (tab) { 660 | //check if this tab is already in the activeTabs array 661 | if ($scope.activeTabs.indexOf(tab) > -1) { 662 | //if so, return true 663 | return true; 664 | } else { 665 | //if not, return false 666 | return false; 667 | } 668 | }; 669 | //function to 'open' a tab 670 | $scope.openTab = function (tab) { 671 | //check if tab is already open 672 | if ($scope.isOpenTab(tab)) { //if it is, remove it from the activeTabs array 673 | $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1); 674 | } else { 675 | //if it's not, add it! 676 | $scope.activeTabs.push(tab); 677 | } 678 | }; 679 | //function to leave a tab open if open or open if not 680 | $scope.leaveOpenTab = function (tab) { 681 | //check if tab is already open 682 | if (!$scope.isOpenTab(tab)) { 683 | //if it is not open, add to array 684 | $scope.activeTabs.push(tab); 685 | } 686 | }; 687 | }); -------------------------------------------------------------------------------- /public/js/tmSrvrServices.js: -------------------------------------------------------------------------------- 1 | /* global TacMapServer, TacMapUnit */ 2 | // ***** SERVER SERVICES ******// 3 | TacMapServer.factory('GeoService', function () { 4 | var geosvc = { 5 | }; 6 | geosvc.entities = []; 7 | geosvc.waypoints = []; 8 | geosvc.missionid = null; 9 | geosvc.sdatasources = []; 10 | geosvc.wpdatasources = []; 11 | geosvc.movementsegments = []; 12 | geosvc.rptInterval = 10; //seconds 13 | geosvc.initGeodesy = function (missionid, missiondata) { 14 | console.log("initGeodesy " + missionid); 15 | geosvc.missionid = missionid; 16 | geosvc.sdatasources[geosvc.missionid] = new Cesium.CustomDataSource(geosvc.missionid); 17 | viewer.dataSources.add(geosvc.sdatasources[geosvc.missionid]); 18 | geosvc.wpdatasources[geosvc.missionid] = new Cesium.CustomDataSource(geosvc.missionid + "WP"); 19 | viewer.dataSources.add(geosvc.wpdatasources[geosvc.missionid]); 20 | //console.log(missiondata); 21 | var polygons = missiondata.Mission.Polygons.Polygon; 22 | var entities = missiondata.Mission.Entities.Entity; 23 | geosvc.entities = missiondata.Mission.Entities.Entity; 24 | geosvc.addPolygons(polygons); 25 | geosvc.addEntities(entities); 26 | //console.log(geosvc.movementsegments); 27 | viewer.zoomTo(geosvc.sdatasources[geosvc.missionid].entities.getById("Default")); 28 | geosvc.setNetViz(entities, ""); 29 | }; 30 | geosvc.addPolygons = function (polygons) { 31 | //console.log('addPolygons ' + polygons.length); 32 | //console.log(polygons); 33 | for (i = 0; i < polygons.length; i++) { 34 | if (polygons[i]._locations.length > 0) { 35 | geosvc.addCesiumPolygon(polygons[i]); 36 | } 37 | } 38 | }; 39 | geosvc.addEntities = function (entities) { 40 | //console.log('addEntities ' + entities.length); 41 | for (i = 0; i < entities.length; i++) { 42 | if (entities[i]._location.length > 0) { 43 | geosvc.addCesiumBillBoard(entities[i]); 44 | } 45 | } 46 | }; 47 | geosvc.addCesiumPolygon = function (poly) { 48 | //console.log('addPolygon'); 49 | var loc = poly._locations; 50 | //console.log(loc); 51 | loc = loc.replace(/\s|\"|\[|\]/g, "").split(","); 52 | //Cartesian wants long, lat 53 | geosvc.sdatasources[geosvc.missionid].entities.add({ 54 | id: poly._id, 55 | name: poly._name, 56 | polygon: { 57 | hierarchy: Cesium.Cartesian3.fromDegreesArray(loc.reverse()), 58 | outline: true, 59 | outlineColor: Cesium.Color[poly._color], 60 | outlineWidth: 2, 61 | fill: false 62 | } 63 | }); 64 | }; 65 | geosvc.addCesiumBillBoard = function (entity) { 66 | var loc = entity._location; 67 | loc = loc.replace(/\s|\"|\[|\]/g, "").split(","); 68 | geosvc.sdatasources[geosvc.missionid].entities.add({ 69 | id: entity._id, 70 | name: entity._name, 71 | position: Cesium.Cartesian3.fromDegrees(loc[1], loc[0]), 72 | billboard: { 73 | image: entity._icon, 74 | width: 40, 75 | height: 25 76 | }, 77 | label: { 78 | text: entity._name, 79 | font: '10pt monospace', 80 | outlineWidth: 2, 81 | verticalOrigin: Cesium.VerticalOrigin.TOP, 82 | pixelOffset: new Cesium.Cartesian2(0, 15) 83 | } 84 | }); 85 | if (entity.waypoints) { 86 | geosvc.addStoredWaypoints(entity); 87 | } 88 | }; 89 | geosvc.addStoredWaypoints = function (entity) { 90 | // console.log("addStoredWaypoints: " + entity._id); 91 | var w = entity.waypoints; 92 | var uid = entity._id; 93 | geosvc.waypoints[uid] = []; 94 | geosvc.waypoints[uid].push(w[0]); 95 | for (p = 1; p < w.length; p++) { 96 | geosvc.waypoints[uid].push(w[p]); 97 | var arr = [w[p - 1][1], w[p - 1][0], w[p][1], w[p][0]]; 98 | geosvc.wpdatasources[geosvc.missionid].entities.add({ 99 | id: uid + 'WP' + geosvc.waypoints[uid].length, 100 | polyline: { 101 | positions: Cesium.Cartesian3.fromDegreesArray(arr), 102 | width: 1, 103 | material: Cesium.Color.LIGHTYELLOW 104 | } 105 | }); 106 | } 107 | geosvc.setMovements(uid, geosvc.waypoints[uid]); 108 | }; 109 | geosvc.setRptInc = function (meters) { 110 | geosvc.rptIncrement = meters; 111 | }; 112 | geosvc.setMovements = function (uid, wpts) { 113 | // console.log('setMovements'); 114 | geosvc.movementsegments[uid] = []; 115 | for (p = 1; p < wpts.length; p++) { 116 | var b = geosvc.calcBearing(wpts[p - 1][0], wpts[p - 1][1], wpts[p][0], wpts[p][1]); 117 | var d = geosvc.calcDistanceMeters(wpts[p - 1][0], wpts[p - 1][1], wpts[p][0], wpts[p][1]); 118 | geosvc.splitLine(uid, wpts[p - 1][0], wpts[p - 1][1], d, b, wpts[p - 1][2]); 119 | } 120 | }; 121 | //Divide a line into increments based on speed and increment//time to get to next point governs interval .. 1000*speed/increment 122 | geosvc.splitLine = function (entityid, startlat, startlng, wpdist, bearing, speed) { 123 | //console.log('splitLine ' + entityid + "," + startlat + "," + startlng + "," + dist + "," + bearing + "," + speed); 124 | var slat = startlat; 125 | var slng = startlng; 126 | //Divide wappoint segment into segments as long as unit will move in geosvc.rptInterval seconds == speed * geosvc.rptInterval 127 | var leglength = speed * geosvc.rptInterval; 128 | var seglength = Math.ceil(wpdist / leglength); 129 | while (seglength > 0) { 130 | geosvc.movementsegments[entityid].push({ 131 | lat: slat, lon: slng 132 | }); 133 | //console.log("move: " + entityid + ": from " + slat + ", " + slng + ", speed:" speed); 134 | var nextloc = geosvc.translateCoord(slat, slng, leglength, bearing); 135 | slat = nextloc[0]; 136 | slng = nextloc[1]; 137 | seglength--; 138 | } 139 | }; 140 | geosvc.calcDistanceMeters = function (lat1, lng1, lat2, lng2) { 141 | var R = 6371000; 142 | // metres 143 | var lr1 = lat1 * Math.PI / 180; //to radians 144 | var lr2 = lat2 * Math.PI / 180; 145 | var lnr1 = lng1 * Math.PI / 180; 146 | var lnr2 = lng2 * Math.PI / 180; 147 | var x = (lnr2 - lnr1) * Math.cos((lr1 + lr2) / 2); 148 | var y = (lr2 - lr1); 149 | var d = Math.sqrt(x * x + y * y) * R; 150 | return d; 151 | }; 152 | geosvc.calcBearing = function (lat1, lng1, lat2, lng2) { 153 | var lr1 = lat1 * Math.PI / 180; // to Radians 154 | var lr2 = lat2 * Math.PI / 180; 155 | var lnr1 = lng1 * Math.PI / 180; 156 | var lnr2 = lng2 * Math.PI / 180; 157 | var y = Math.sin(lnr2 - lnr1) * Math.cos(lr2); 158 | var x = Math.cos(lr1) * Math.sin(lr2) - Math.sin(lr1) * Math.cos(lr2) * Math.cos(lnr2 - lnr1); 159 | var brng = Math.atan2(y, x) * 180 / Math.PI; 160 | return brng; 161 | }; 162 | geosvc.translateCoord = function (lat, lon, distance, bearing) { 163 | var coord = []; 164 | var R = 6371000; 165 | // meters , earth Radius approx 166 | var PI = 3.1415926535; 167 | var RADIANS = PI / 180; 168 | var DEGREES = 180 / PI; 169 | var lat2; 170 | var lon2; 171 | var lat1 = lat * RADIANS; 172 | var lon1 = lon * RADIANS; 173 | var radbearing = bearing * RADIANS; 174 | lat2 = Math.asin(Math.sin(lat1) * Math.cos(distance / R) + Math.cos(lat1) * Math.sin(distance / R) * Math.cos(radbearing)); 175 | lon2 = lon1 + Math.atan2(Math.sin(radbearing) * Math.sin(distance / R) * Math.cos(lat1), Math.cos(distance / R) - Math.sin(lat1) * Math.sin(lat2)); 176 | coord = [lat2 * DEGREES, lon2 * DEGREES]; 177 | return (coord); 178 | }; 179 | geosvc.setNetViz = function (e, netsel) { 180 | geosvc.entities = e; 181 | for (i = 0; i < e.length; i++) { 182 | if (netsel[e[i]._network] && geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id)) { 183 | if (netsel[e[i]._network].show) { 184 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = true; 185 | geosvc.showWP(e[i]._id); 186 | } else { 187 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = false; 188 | geosvc.hideWP(e[i]._id); 189 | } 190 | } 191 | else if (geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id)) { 192 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = false; 193 | geosvc.hideWP(e[i]._id); 194 | } 195 | } 196 | }; 197 | geosvc.showWP = function (uid) { 198 | if (geosvc.waypoints[uid]) { 199 | for (w = 1; w < geosvc.waypoints[uid].length + 1; w++) { 200 | if (geosvc.wpdatasources[geosvc.missionid].entities.getById(uid + 'WP' + w)) { 201 | geosvc.wpdatasources[geosvc.missionid].entities.getById(uid + 'WP' + w).show = true; 202 | } 203 | } 204 | } 205 | }; 206 | geosvc.hideWP = function (uid) { 207 | if (geosvc.waypoints[uid]) { 208 | for (w = 1; w < geosvc.waypoints[uid].length + 1; w++) { 209 | if (geosvc.wpdatasources[geosvc.missionid].entities.getById(uid + 'WP' + w)) { 210 | geosvc.wpdatasources[geosvc.missionid].entities.getById(uid + 'WP' + w).show = false; 211 | } 212 | } 213 | } 214 | }; 215 | geosvc.hideAllWP = function () { 216 | console.log("hideAllWP .. " + geosvc.missionid) 217 | var wpent = geosvc.wpdatasources[geosvc.missionid].entities.values; 218 | for (p = 0; p < wpent.length; p++) { 219 | geosvc.wpdatasources[geosvc.missionid].entities.getById(wpent[p].id).show = false; 220 | } 221 | }; 222 | geosvc.showAllWP = function () { 223 | console.log("showAllWP .. " + geosvc.missionid) 224 | var wpent = geosvc.wpdatasources[geosvc.missionid].entities.values; 225 | for (p = 0; p < wpent.length; p++) { 226 | geosvc.wpdatasources[geosvc.missionid].entities.getById(wpent[p].id).show = true; 227 | } 228 | }; 229 | geosvc.joinNetworks = function (entities, networks, msgsvc, $scope) { 230 | console.log("joinNetworks"); 231 | for (e = 0; e < entities.length; e++) { 232 | if ($scope.netselected[entities[e]._network]) { 233 | $scope.netselected[entities[e]._network].show === true; 234 | $scope.netselected[entities[e]._network].network = entities[e]._network; 235 | geosvc.setNetViz(entities, $scope.netselected); 236 | } else { 237 | $scope.netselected[entities[e]._network] = []; 238 | $scope.netselected[entities[e]._network].show = true; 239 | $scope.netselected[entities[e]._network].network = entities[e]._network; 240 | geosvc.setNetViz(entities, $scope.netselected); 241 | } 242 | } 243 | for (n = 0; n < networks.length; n++) { 244 | msgsvc.joinNet(networks[n]._name); 245 | } 246 | }; 247 | return geosvc; 248 | }); 249 | TacMapServer.factory('MsgService', function () { 250 | var msgsvc = { 251 | }; 252 | msgsvc.serverid; 253 | msgsvc.missionid; 254 | msgsvc.connected = false; 255 | msgsvc.sending = false; 256 | msgsvc.lastSendingTime = 0; 257 | msgsvc.units = []; 258 | msgsvc.socket = io(); 259 | // Sends a message 260 | msgsvc.joinNet = function (netname) { 261 | msgsvc.socket.emit('server join', { 262 | serverid: msgsvc.serverid, netname: netname 263 | }); 264 | }; 265 | msgsvc.leaveNet = function (netname) { 266 | msgsvc.socket.emit('server leave', { 267 | serverid: msgsvc.serverid, netname: netname 268 | }); 269 | }; 270 | msgsvc.setMission = function (name, missiondata) { 271 | msgsvc.socket.emit('set mission', { 272 | missionid: name, missiondata: missiondata 273 | }); 274 | }; 275 | msgsvc.sendMessage = function (msg) { 276 | var message = msg; 277 | // if there is a non-empty message and a socket connection 278 | if (message && msgsvc.connected) { 279 | console.log("sendMessage from " + message.unit + " at " + message.time + " posrep: " + message.position[0] + ", " + message.position[1]); 280 | // tell server to execute 'new message' and send along one parameter 281 | msgsvc.socket.emit('send msg', { 282 | message: message 283 | }); 284 | } 285 | }; 286 | msgsvc.connectServer = function (data, sname, missionjson) { 287 | console.log(data.message + " " + data.socketid); 288 | msgsvc.connected = true; 289 | msgsvc.missionid = sname; 290 | //console.log(missionjson); 291 | msgsvc.socket.emit('server connected', { 292 | message: 'server', socketid: data.socketid, missionid: msgsvc.missionid, missiondata: missionjson 293 | }); 294 | }; 295 | msgsvc.disconnectServer = function (data) { 296 | console.log("Server Disconnected " + data.socketid); 297 | msgsvc.connected = false; 298 | msgsvc.socket.emit('server disconnected', { 299 | message: 'server', socketid: data.socketid, mission: msgsvc.missionid 300 | }); 301 | }; 302 | return msgsvc; 303 | }); 304 | TacMapServer.factory('DlgBx', function ($window, $q) { 305 | var dlg = { 306 | }; 307 | dlg.alert = function alert(message) { 308 | var defer = $q.defer(); 309 | $window.alert(message); 310 | defer.resolve(); 311 | return (defer.promise); 312 | }; 313 | dlg.prompt = function prompt(message, defaultValue) { 314 | var defer = $q.defer(); 315 | // The native prompt will return null or a string. 316 | var response = $window.prompt(message, defaultValue); 317 | if (response === null) { 318 | defer.reject(); 319 | } else { 320 | defer.resolve(response); 321 | } 322 | return (defer.promise); 323 | }; 324 | dlg.confirm = function confirm(message) { 325 | var defer = $q.defer(); 326 | // The native confirm will return a boolean. 327 | if ($window.confirm(message)) { 328 | defer.resolve(true); 329 | } else { 330 | defer.reject(false); 331 | } 332 | return (defer.promise); 333 | }; 334 | return dlg; 335 | }); -------------------------------------------------------------------------------- /public/js/tmUnitControllers.js: -------------------------------------------------------------------------------- 1 | // ***** UNIT CONTROLLERS ******// 2 | TacMapUnit.controller('viewCtl', function ($indexedDB, $scope, $http, GeoUnitService, MsgUnitService) { 3 | var vwctl = this; 4 | console.log("viewCtl"); 5 | var dB = $indexedDB; 6 | var ellipsoid = scene.globe.ellipsoid; 7 | vwctl.unitselected = null; 8 | // 9 | vwctl.entities = []; 10 | vwctl.networks = []; 11 | vwctl.loc = []; 12 | $scope.netselected = []; 13 | $scope.selmission = { id: 0, name: 'Default Mission' }; 14 | vwctl.missionid = GeoUnitService.missionid; 15 | // 16 | vwctl.lftClickHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); 17 | vwctl.lftClickHandler.setInputAction(function (mouse) { 18 | var pickedObject = scene.pick(mouse.position); 19 | if (Cesium.defined(pickedObject) && pickedObject.id.position !== undefined && pickedObject.id.billboard) { 20 | vwctl.selectUnit(pickedObject.id); 21 | } else { 22 | vwctl.loc = []; 23 | vwctl.unitselected = null; 24 | $scope.$apply(); 25 | } 26 | }, 27 | Cesium.ScreenSpaceEventType.LEFT_CLICK); 28 | // 29 | vwctl.selectUnit = function (u, zoomto) { 30 | //console.log(u._id); 31 | vwctl.unitselected = GeoUnitService.sdatasources[$scope.selmission.value].entities.getById(u._id); 32 | vwctl.loc = vwctl.getLoc(vwctl.unitselected); 33 | if (zoomto) { 34 | GeoUnitService.sdatasources[$scope.selmission.value].selectedEntity = vwctl.unitselected; 35 | viewer.selectedEntity = vwctl.unitselected; 36 | viewer.camera.flyTo({ 37 | destination: Cesium.Cartesian3.fromDegrees(vwctl.loc[1], vwctl.loc[0], 10000.0), 38 | duration: 1 39 | }); 40 | } else { 41 | $scope.$apply(); 42 | } 43 | }; 44 | vwctl.getLoc = function (entity) { 45 | var cartesian = entity.position.getValue(); 46 | var cartographic = ellipsoid.cartesianToCartographic(cartesian); 47 | var latitudeString = Cesium.Math.toDegrees(cartographic.latitude); 48 | var longitudeString = Cesium.Math.toDegrees(cartographic.longitude); 49 | entity.description = "Location: " + latitudeString + ", " + longitudeString; 50 | return ([latitudeString, longitudeString]); 51 | }; 52 | vwctl.showUnit = function (unit) { 53 | if ($scope.netselected[unit._network]) { 54 | return $scope.netselected[unit._network].show; 55 | } 56 | }; 57 | vwctl.selectNetwork = function (net) { 58 | //console.log("selectNetwork " + net._name); 59 | vwctl.entities = GeoUnitService.entities; 60 | if ($scope.netselected[net._name]) { 61 | if ($scope.netselected[net._name].show === true) { 62 | $scope.netselected[net._name].show = false; 63 | MsgUnitService.leaveNet(net._name); 64 | } else { 65 | $scope.netselected[net._name].show = true; 66 | $scope.netselected[net._name].network = net._name; 67 | MsgUnitService.joinNet(net._name); 68 | } 69 | GeoUnitService.setNetViz(vwctl.entities, $scope.netselected); 70 | return $scope.netselected[net._name].show; 71 | } else { 72 | $scope.netselected[net._name] = []; 73 | $scope.netselected[net._name].show = true; 74 | $scope.netselected[net._name].network = net._name; 75 | GeoUnitService.setNetViz(vwctl.entities, $scope.netselected); 76 | MsgUnitService.joinNet(net._name); 77 | return $scope.netselected[GeoUnitService.entities, net._name].show; 78 | } 79 | }; 80 | vwctl.netSelected = function (net) { 81 | if ($scope.netselected[net._name]) { 82 | return $scope.netselected[net._name].show; 83 | } 84 | }; 85 | MsgUnitService.socket.on('set mission', function (data) { 86 | console.log('set mission'); 87 | $scope.netselected = []; 88 | viewer.dataSources.remove(GeoUnitService.sdatasources[$scope.selmission.value]); 89 | dB.openStore("Missions", function (store) { 90 | store.upsert({ name: data.missionid, data: data.missiondata }); 91 | }).then(function () { 92 | $scope.selmission.value = data.missionid; 93 | vwctl.entities = data.missiondata.Mission.Entities.Entity; 94 | vwctl.networks = data.missiondata.Mission.Networks.Network; 95 | GeoUnitService.initGeodesy(data.missionid, data.missiondata); 96 | GeoUnitService.joinNetworks(vwctl.entities, vwctl.networks, MsgUnitService, $scope); 97 | }); 98 | }); 99 | }); 100 | TacMapUnit.controller('UnitMesssageCtl', function ($indexedDB, $scope, MsgUnitService, GeoUnitService) { 101 | var msgctl = this; 102 | msgctl.dB = $indexedDB; 103 | msgctl.messages = []; 104 | msgctl.moveUnit = function (uid, lat, lon) { 105 | console.log("moveUnit: " + uid); 106 | GeoUnitService.sdatasources[$scope.selmission.value].entities.getById(uid).position = Cesium.Cartesian3.fromDegrees(lon, lat); 107 | }; 108 | MsgUnitService.socket.on('error', console.error.bind(console)); 109 | // MsgUnitService.socket.on('message', console.log.bind(console)); 110 | MsgUnitService.socket.on('connection', function (data) { 111 | console.log("Unit Connected " + data.socketid); 112 | MsgUnitService.unitid = data.socketid; 113 | MsgUnitService.socket.emit('unit connected', { message: 'unit', id: data.socketid }); 114 | msgctl.messages.push({ text: "Unit Connected" }); 115 | }); 116 | MsgUnitService.socket.on('send msg', function (data) { 117 | console.log("receive msg " + data.net + ": " + data.message.unit + " " + data.message.position[0] + ", " + data.message.position[1]); 118 | msgctl.messages.push({ text: "POSREP " + data.net + " " + data.message.unit }); 119 | msgctl.moveUnit(data.message.unit, data.message.position[0], data.message.position[1]); 120 | }); 121 | MsgUnitService.socket.on('unit disconnected', function (data) { 122 | console.log("Unit disconnected " + data.socketid); 123 | msgctl.messages.push({ text: "Unit disconnected" }); 124 | }); 125 | MsgUnitService.socket.on('unit joined', function (data) { 126 | //console.log('Joined Network: ' + data.netname); 127 | msgctl.messages.push({ text: 'Joined Network: ' + data.netname }); 128 | }); 129 | MsgUnitService.socket.on('unit left', function (data) { 130 | console.log('Left Network: ' + data.netname); 131 | msgctl.messages.push({ text: 'Left Network: ' + data.netname }); 132 | }); 133 | MsgUnitService.socket.on('server joined', function (data) { 134 | //console.log('Server ' + data.serverid + ' Joined Net: ' + data.netname); 135 | msgctl.messages.push({ text: 'Server ' + data.serverid + ' Joined Net: ' + data.netname }); 136 | }); 137 | MsgUnitService.socket.on('server left', function (data) { 138 | console.log('Server ' + data.serverid + ' Left Net: ' + data.netname); 139 | msgctl.messages.push({ text: 'Server ' + data.serverid + ' Left Net: ' + data.netname }); 140 | }); 141 | MsgUnitService.socket.on('move unit', function (data) { 142 | msgctl.moveUnit(data.uid, data.lat, data.lon); 143 | }); 144 | }); 145 | TacMapUnit.controller('menuCtrl', function ($scope) { 146 | //initiate an array to hold all active tabs 147 | $scope.activeTabs = []; 148 | //check if the tab is active 149 | $scope.isOpenTab = function (tab) { 150 | //check if this tab is already in the activeTabs array 151 | if ($scope.activeTabs.indexOf(tab) > -1) { 152 | //if so, return true 153 | return true; 154 | } else { 155 | //if not, return false 156 | return false; 157 | } 158 | }; 159 | //function to 'open' a tab 160 | $scope.openTab = function (tab) { 161 | //check if tab is already open 162 | if ($scope.isOpenTab(tab)) { 163 | //if it is, remove it from the activeTabs array 164 | $scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1); 165 | } else { 166 | //if it's not, add it! 167 | $scope.activeTabs.push(tab); 168 | } 169 | }; 170 | //function to leave a tab open if open or open if not 171 | $scope.leaveOpenTab = function (tab) { 172 | //check if tab is already open 173 | if (!$scope.isOpenTab(tab)) { 174 | //if it is not open, add to array 175 | $scope.activeTabs.push(tab); 176 | } 177 | }; 178 | }); 179 | -------------------------------------------------------------------------------- /public/js/tmUnitServices.js: -------------------------------------------------------------------------------- 1 | /* global TacMapUnit */ 2 | // ***** UNIT SERVICES ******// 3 | TacMapUnit.factory('GeoUnitService', function () { 4 | var geosvc = { 5 | }; 6 | geosvc.entities = []; 7 | geosvc.polygons = []; 8 | geosvc.missionid = null; 9 | geosvc.sdatasources = []; 10 | geosvc.initGeodesy = function (missionid, missiondata) { 11 | console.log("initGeodesy " + missionid); 12 | geosvc.missionid = missionid; 13 | geosvc.sdatasources[geosvc.missionid] = new Cesium.CustomDataSource(geosvc.missionid); 14 | viewer.dataSources.add(geosvc.sdatasources[geosvc.missionid]); 15 | //console.log(missiondata); 16 | geosvc.entities = missiondata.Mission.Entities.Entity; 17 | geosvc.polygons = missiondata.Mission.Polygons.Polygon; 18 | geosvc.addPolygons(geosvc.polygons); 19 | geosvc.addEntities(geosvc.entities); 20 | //console.log(geosvc.movementsegments); 21 | viewer.zoomTo(geosvc.sdatasources[geosvc.missionid].entities.getById("Default")); 22 | geosvc.setNetViz(geosvc.entities, ""); 23 | }; 24 | geosvc.addPolygons = function (polygons) { 25 | for (i = 0; i < polygons.length; i++) { 26 | geosvc.addCesiumPolygon(polygons[i]); 27 | } 28 | }; 29 | geosvc.addEntities = function (entities) { 30 | for (i = 0; i < entities.length; i++) { 31 | geosvc.entities[entities[i]._id] = entities[i]; 32 | geosvc.addCesiumBillBoard(entities[i]); 33 | } 34 | }; 35 | geosvc.addCesiumPolygon = function (poly) { 36 | var loc = poly._locations; 37 | loc = loc.replace(/\s|\"|\[|\]/g, "").split(","); 38 | //Cartesian wants long, lat 39 | geosvc.sdatasources[geosvc.missionid].entities.add({ 40 | id: poly._id, 41 | name: poly._name, 42 | polygon: { 43 | hierarchy: Cesium.Cartesian3.fromDegreesArray(loc.reverse()), 44 | outline: true, 45 | outlineColor: Cesium.Color[poly._color], 46 | outlineWidth: 2, 47 | fill: false 48 | } 49 | }); 50 | }; 51 | geosvc.addCesiumBillBoard = function (entity) { 52 | var loc = entity._location; 53 | loc = loc.replace(/\s|\"|\[|\]/g, "").split(","); 54 | geosvc.sdatasources[geosvc.missionid].entities.add({ 55 | id: entity._id, 56 | name: entity._name, 57 | position: Cesium.Cartesian3.fromDegrees(loc[1], loc[0]), 58 | billboard: { 59 | image: entity._icon, 60 | width: 40, 61 | height: 25 62 | }, 63 | label: { 64 | text: entity._name, 65 | font: '10pt monospace', 66 | outlineWidth: 2, 67 | verticalOrigin: Cesium.VerticalOrigin.TOP, 68 | pixelOffset: new Cesium.Cartesian2(0, 15) 69 | } 70 | }); 71 | }; 72 | geosvc.setNetViz = function (e, netsel) { 73 | geosvc.entities = e; 74 | for (i = 0; i < e.length; i++) { 75 | if (netsel[e[i]._network] && geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id)) { 76 | if (netsel[e[i]._network].show) { 77 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = true; 78 | } else { 79 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = false; 80 | } 81 | } else if (geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id)) { 82 | geosvc.sdatasources[geosvc.missionid].entities.getById(e[i]._id).show = false; 83 | } 84 | } 85 | }; 86 | geosvc.joinNetworks = function (entities, networks, msgsvc, $scope) { 87 | for (e = 0; e < entities.length; e++) { 88 | if ($scope.netselected[entities[e]._network]) { 89 | $scope.netselected[entities[e]._network].show === true; 90 | $scope.netselected[entities[e]._network].network = entities[e]._network; 91 | geosvc.setNetViz(entities, $scope.netselected); 92 | } else { 93 | $scope.netselected[entities[e]._network] = []; 94 | $scope.netselected[entities[e]._network].show = true; 95 | $scope.netselected[entities[e]._network].network = entities[e]._network; 96 | geosvc.setNetViz(entities, $scope.netselected); 97 | } 98 | } 99 | for (n = 0; n < networks.length; n++) { 100 | msgsvc.joinNet(networks[n]._name); 101 | } 102 | }; 103 | return geosvc; 104 | }); 105 | TacMapUnit.factory('MsgUnitService', function () { 106 | var msgsvc = { 107 | }; 108 | msgsvc.unitid; 109 | msgsvc.missionid; 110 | msgsvc.connected = false; 111 | msgsvc.sending = false; 112 | msgsvc.lastSendingTime = 0; 113 | msgsvc.messagelist = []; 114 | msgsvc.socket = io(); 115 | // Sends a message 116 | msgsvc.joinNet = function (netname) { 117 | if (!msgsvc.messagelist[netname]) { 118 | msgsvc.messagelist[netname] = []; 119 | } 120 | msgsvc.socket.emit('unit join', {unitid: msgsvc.unitid, netname: netname}); 121 | }; 122 | msgsvc.leaveNet = function (netname) { 123 | msgsvc.socket.emit('unit leave', {unitid: msgsvc.unitid, netname: netname}); 124 | }; 125 | return msgsvc; 126 | }); 127 | TacMapUnit.factory('DlgBx', function ($window, $q) { 128 | var dlg = { 129 | }; 130 | dlg.alert = function alert(message) { 131 | var defer = $q.defer(); 132 | $window.alert(message); 133 | defer.resolve(); 134 | return (defer.promise); 135 | }; 136 | dlg.prompt = function prompt(message, defaultValue) { 137 | var defer = $q.defer(); 138 | // The native prompt will return null or a string. 139 | var response = $window.prompt(message, defaultValue); 140 | if (response === null) { 141 | defer.reject(); 142 | } else { 143 | defer.resolve(response); 144 | } 145 | return (defer.promise); 146 | }; 147 | dlg.confirm = function confirm(message) { 148 | var defer = $q.defer(); 149 | // The native confirm will return a boolean. 150 | if ($window.confirm(message)) { 151 | defer.resolve(true); 152 | } else { 153 | defer.reject(false); 154 | } 155 | return (defer.promise); 156 | }; 157 | return dlg; 158 | }); -------------------------------------------------------------------------------- /public/json/missions.json: -------------------------------------------------------------------------------- 1 | [{"id":"0","name":"Default","url":"json/default.json"},{"id":"1","name":"TRAP","url":"json/trap.json"},{"id":"2","name":"CAMPEN","url":"json/campen.json"}] -------------------------------------------------------------------------------- /public/server.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | TAC MAP SERVER 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
{{selmission.name}}
32 |
33 |
34 |
Missions 35 |
36 |
37 | 39 |
40 |
41 |
42 |
Plan Mission
43 |
44 |
45 |
Missions:
46 | 48 | 50 | 51 | 52 |
53 |
Reset Local Cache 54 | Database
55 |
56 | 58 | Set Locations 59 |
60 |
Right Click to Set Location
61 |
62 | 64 | Edit Waypoints 65 | 66 | 68 | 70 | 71 |
72 |
Right Click to Set Waypoints
73 |
74 | Speed: 75 | 77 |
78 |
79 |
80 |
81 | {{f.name}} 82 |
83 |
84 |
85 |
86 |
87 |
88 |
Run 89 | Mission
90 |
92 |
93 | 95 | 96 | 97 | 98 |
99 |
Mission Running
100 |
101 | Timer: {{mctl.time}}s 102 | Show Waypoints 105 |
106 |
107 |
108 |
109 |
NETWORKS
110 |
111 |
112 |
113 |
116 | 117 |
{{n._name}}
118 |
119 |
120 |
121 |
122 |
123 |
124 |
UNITS
125 |
126 |
127 |
128 |
131 | 132 |
{{u._name}}
133 |
134 |
135 |
136 |
137 |
138 |
139 |
MESSAGE LOG
140 |
141 |
142 |
143 |
144 | {{m.text}} 145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /public/unit.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | TACMAP UNIT 23 | 24 | 25 | 26 | 27 |
28 |
29 |
{{selmission.value}}
30 |
31 |
32 |
NETWORKS
34 |
35 |
36 |
37 |
40 | 41 |
{{n._name}}
42 |
43 |
44 |
45 |
46 |
47 |
UNITS
49 |
50 |
51 |
52 |
56 | 57 |
{{u._name}}
58 |
59 |
60 |
61 |
62 |
63 |
MESSAGE LOG
65 |
66 |
67 |
68 |
69 | {{m.text}} 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /public/xml/CAMPEN.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /public/xml/DefaultMission.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /public/xml/TRAP.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 18 | 31 | 32 | 33 | 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /public/xml/gpx.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp 11 | 12 | GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units. 13 | 14 | 15 | 16 | 17 | 18 | 19 | GPX is the root element in the XML file. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements 28 | to the extensions section of the GPX document. 29 | 30 | 31 | 32 | 33 | 34 | 35 | Metadata about the file. 36 | 37 | 38 | 39 | 40 | 41 | 42 | A list of waypoints. 43 | 44 | 45 | 46 | 47 | 48 | 49 | A list of routes. 50 | 51 | 52 | 53 | 54 | 55 | 56 | A list of tracks. 57 | 58 | 59 | 60 | 61 | 62 | 63 | You can add extend GPX by adding your own elements from another schema here. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | You must include the version number in your GPX document. 73 | 74 | 75 | 76 | 77 | 78 | 79 | You must include the name or URL of the software that created your GPX document. This allows others to 80 | inform the creator of a GPX instance document that fails to validate. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich, 90 | meaningful information about your GPX files allows others to search for and use your GPS data. 91 | 92 | 93 | 94 | 95 | 96 | 97 | The name of the GPX file. 98 | 99 | 100 | 101 | 102 | 103 | 104 | A description of the contents of the GPX file. 105 | 106 | 107 | 108 | 109 | 110 | 111 | The person or organization who created the GPX file. 112 | 113 | 114 | 115 | 116 | 117 | 118 | Copyright and license information governing use of the file. 119 | 120 | 121 | 122 | 123 | 124 | 125 | URLs associated with the location described in the file. 126 | 127 | 128 | 129 | 130 | 131 | 132 | The creation date of the file. 133 | 134 | 135 | 136 | 137 | 138 | 139 | Keywords associated with the file. Search engines or databases can use this information to classify the data. 140 | 141 | 142 | 143 | 144 | 145 | 146 | Minimum and maximum coordinates which describe the extent of the coordinates in the file. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | You can add extend GPX by adding your own elements from another schema here. 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | wpt represents a waypoint, point of interest, or named feature on a map. 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | Elevation (in meters) of the point. 173 | 174 | 175 | 176 | 177 | 178 | 179 | Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. 180 | 181 | 182 | 183 | 184 | 185 | 186 | Magnetic variation (in degrees) at the point 187 | 188 | 189 | 190 | 191 | 192 | 193 | Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message. 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS. 203 | 204 | 205 | 206 | 207 | 208 | 209 | GPS waypoint comment. Sent to GPS as comment. 210 | 211 | 212 | 213 | 214 | 215 | 216 | A text description of the element. Holds additional information about the element intended for the user, not the GPS. 217 | 218 | 219 | 220 | 221 | 222 | 223 | Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g. 224 | 225 | 226 | 227 | 228 | 229 | 230 | Link to additional information about the waypoint. 231 | 232 | 233 | 234 | 235 | 236 | 237 | Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out. 238 | 239 | 240 | 241 | 242 | 243 | 244 | Type (classification) of the waypoint. 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Type of GPX fix. 254 | 255 | 256 | 257 | 258 | 259 | 260 | Number of satellites used to calculate the GPX fix. 261 | 262 | 263 | 264 | 265 | 266 | 267 | Horizontal dilution of precision. 268 | 269 | 270 | 271 | 272 | 273 | 274 | Vertical dilution of precision. 275 | 276 | 277 | 278 | 279 | 280 | 281 | Position dilution of precision. 282 | 283 | 284 | 285 | 286 | 287 | 288 | Number of seconds since last DGPS update. 289 | 290 | 291 | 292 | 293 | 294 | 295 | ID of DGPS station used in differential correction. 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | You can add extend GPX by adding your own elements from another schema here. 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | The latitude of the point. This is always in decimal degrees, and always in WGS84 datum. 313 | 314 | 315 | 316 | 317 | 318 | 319 | The longitude of the point. This is always in decimal degrees, and always in WGS84 datum. 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination. 329 | 330 | 331 | 332 | 333 | 334 | 335 | GPS name of route. 336 | 337 | 338 | 339 | 340 | 341 | 342 | GPS comment for route. 343 | 344 | 345 | 346 | 347 | 348 | 349 | Text description of route for user. Not sent to GPS. 350 | 351 | 352 | 353 | 354 | 355 | 356 | Source of data. Included to give user some idea of reliability and accuracy of data. 357 | 358 | 359 | 360 | 361 | 362 | 363 | Links to external information about the route. 364 | 365 | 366 | 367 | 368 | 369 | 370 | GPS route number. 371 | 372 | 373 | 374 | 375 | 376 | 377 | Type (classification) of route. 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | You can add extend GPX by adding your own elements from another schema here. 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | A list of route points. 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | trk represents a track - an ordered list of points describing a path. 404 | 405 | 406 | 407 | 408 | 409 | 410 | GPS name of track. 411 | 412 | 413 | 414 | 415 | 416 | 417 | GPS comment for track. 418 | 419 | 420 | 421 | 422 | 423 | 424 | User description of track. 425 | 426 | 427 | 428 | 429 | 430 | 431 | Source of data. Included to give user some idea of reliability and accuracy of data. 432 | 433 | 434 | 435 | 436 | 437 | 438 | Links to external information about track. 439 | 440 | 441 | 442 | 443 | 444 | 445 | GPS track number. 446 | 447 | 448 | 449 | 450 | 451 | 452 | Type (classification) of track. 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | You can add extend GPX by adding your own elements from another schema here. 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data. 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | You can add extend GPX by adding your own elements from another schema here. 479 | 480 | 481 | 482 | 483 | 484 | 485 | You can add extend GPX by adding your own elements from another schema here. 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data. 496 | 497 | 498 | 499 | 500 | 501 | 502 | A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track. 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | You can add extend GPX by adding your own elements from another schema here. 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | Information about the copyright holder and any license governing use of this file. By linking to an appropriate license, 521 | you may place your data into the public domain or grant additional usage rights. 522 | 523 | 524 | 525 | 526 | 527 | 528 | Year of copyright. 529 | 530 | 531 | 532 | 533 | 534 | 535 | Link to external file containing license text. 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | Copyright holder (TopoSoft, Inc.) 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | A link to an external resource (Web page, digital photo, video clip, etc) with additional information. 553 | 554 | 555 | 556 | 557 | 558 | 559 | Text of hyperlink. 560 | 561 | 562 | 563 | 564 | 565 | 566 | Mime type of content (image/jpeg) 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | URL of hyperlink. 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | An email address. Broken into two parts (id and domain) to help prevent email harvesting. 584 | 585 | 586 | 587 | 588 | 589 | id half of email address (billgates2004) 590 | 591 | 592 | 593 | 594 | 595 | 596 | domain half of email address (hotmail.com) 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | A person or organization. 606 | 607 | 608 | 609 | 610 | 611 | 612 | Name of person or organization. 613 | 614 | 615 | 616 | 617 | 618 | 619 | Email address. 620 | 621 | 622 | 623 | 624 | 625 | 626 | Link to Web site or other external information about person. 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | A geographic point with optional elevation and time. Available for use by other schemas. 637 | 638 | 639 | 640 | 641 | 642 | 643 | The elevation (in meters) of the point. 644 | 645 | 646 | 647 | 648 | 649 | 650 | The time that the point was recorded. 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | The latitude of the point. Decimal degrees, WGS84 datum. 659 | 660 | 661 | 662 | 663 | 664 | 665 | The latitude of the point. Decimal degrees, WGS84 datum. 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | An ordered sequence of points. (for polygons or polylines, e.g.) 675 | 676 | 677 | 678 | 679 | 680 | 681 | Ordered list of geographic points. 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | Two lat/lon pairs defining the extent of an element. 692 | 693 | 694 | 695 | 696 | 697 | The minimum latitude. 698 | 699 | 700 | 701 | 702 | 703 | 704 | The minimum longitude. 705 | 706 | 707 | 708 | 709 | 710 | 711 | The maximum latitude. 712 | 713 | 714 | 715 | 716 | 717 | 718 | The maximum longitude. 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | The latitude of the point. Decimal degrees, WGS84 datum. 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | The longitude of the point. Decimal degrees, WGS84 datum. 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | Used for bearing, heading, course. Units are decimal degrees, true (not magnetic). 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | Represents a differential GPS station. 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | -------------------------------------------------------------------------------- /public/xml/missions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/xml/speeds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/xml/symbols.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tacmap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 jdn 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | (function () { 18 | "use strict"; 19 | var express = require('express'); 20 | var compression = require('compression'); 21 | var url = require('url'); 22 | var request = require('axios'); 23 | var bodyParser = require('body-parser'); 24 | var fs = require('fs'); 25 | var cesium = require('./geoserver/cesiumserver'); 26 | // 27 | var cors = require('cors'); 28 | 29 | var server_port = 9090; 30 | var yargs = require('yargs').options({ 31 | 'port': { 32 | 'default': server_port, 33 | 'description': 'Port to listen on.' 34 | }, 35 | 'public': { 36 | 'type': 'boolean', 37 | 'description': 'Run a public server that listens on all interfaces.' 38 | }, 39 | 'publicssl': { 40 | 'type': 'boolean', 41 | 'description': 'Run a public https server that listens on all interfaces.' 42 | }, 43 | 'upstream-proxy': { 44 | 'description': 'A standard proxy server that will be used to retrieve data. Specify a URL including port, e.g. "http://proxy:8000".' 45 | }, 46 | 'bypass-upstream-proxy-hosts': { 47 | 'description': 'A comma separated list of hosts that will bypass the specified upstream_proxy, e.g. "lanhost1,lanhost2"' 48 | }, 49 | 'help': { 50 | 'alias': 'h', 51 | 'type': 'boolean', 52 | 'description': 'Show this help.' 53 | } 54 | }); 55 | var argv = yargs.argv; 56 | if (argv.help) { 57 | return yargs.showHelp(); 58 | } 59 | var app = express(); 60 | app.use(bodyParser.json()); 61 | app.use(express.static(__dirname)); 62 | app.use(express.static(__dirname + '/public')); 63 | app.use(compression()); 64 | app.use(cors()); 65 | 66 | function getRemoteUrlFromParam(req) { 67 | var remoteUrl = req.params[0]; 68 | if (remoteUrl) { 69 | // add http:// to the URL if no protocol is present 70 | if (!/^https?:\/\//.test(remoteUrl)) { 71 | remoteUrl = 'http://' + remoteUrl; 72 | } 73 | remoteUrl = new URL(remoteUrl); 74 | // copy query string 75 | remoteUrl.search = new URL(req.url).search; 76 | } 77 | return remoteUrl; 78 | } 79 | 80 | var dontProxyHeaderRegex = /^(?:Host|Proxy-Connection|Connection|Keep-Alive|Transfer-Encoding|TE|Trailer|Proxy-Authorization|Proxy-Authenticate|Upgrade)$/i; 81 | 82 | function filterHeaders(req, headers) { 83 | var result = {}; 84 | // filter out headers that are listed in the regex above 85 | Object.keys(headers).forEach(function (name) { 86 | if (!dontProxyHeaderRegex.test(name)) { 87 | result[name] = headers[name]; 88 | } 89 | }); 90 | return result; 91 | } 92 | 93 | var upstreamProxy = argv['upstream-proxy']; 94 | var bypassUpstreamProxyHosts = {}; 95 | if (argv['bypass-upstream-proxy-hosts']) { 96 | argv['bypass-upstream-proxy-hosts'].split(',').forEach(function (host) { 97 | bypassUpstreamProxyHosts[host.toLowerCase()] = true; 98 | }); 99 | } 100 | app.get('/proxy/*', function (req, res, next) { 101 | // look for request like http://localhost:8080/proxy/http://example.com/file?query=1 102 | var remoteUrl = getRemoteUrlFromParam(req); 103 | if (!remoteUrl) { 104 | // look for request like http://localhost:8080/proxy/?http%3A%2F%2Fexample.com%2Ffile%3Fquery%3D1 105 | remoteUrl = Object.keys(req.query)[0]; 106 | if (remoteUrl) { 107 | remoteUrl = new URL(remoteUrl); 108 | } 109 | } 110 | 111 | if (!remoteUrl) { 112 | return res.send(400, 'No url specified.'); 113 | } 114 | 115 | if (!remoteUrl.protocol) { 116 | remoteUrl.protocol = 'http:'; 117 | } 118 | 119 | var proxy; 120 | if (upstreamProxy && !(remoteUrl.host in bypassUpstreamProxyHosts)) { 121 | proxy = upstreamProxy; 122 | } 123 | 124 | // encoding : null means "body" passed to the callback will be raw bytes 125 | 126 | request.get({ 127 | url: url.format(remoteUrl), 128 | headers: filterHeaders(req, req.headers), 129 | encoding: null, 130 | proxy: proxy 131 | }, function (error, response, body) { 132 | var code = 500; 133 | if (response) { 134 | code = response.statusCode; 135 | res.header(filterHeaders(req, response.headers)); 136 | } 137 | 138 | res.send(code, body); 139 | }); 140 | }); 141 | var server_ip_address = '127.0.0.1'; 142 | var server = app.listen(server_port, argv.public ? undefined : server_ip_address, function () { 143 | if (argv.public) { 144 | console.log('TacMap development server running publicly. Connect to http://localhost:%d/', server.address().port); 145 | } 146 | else if (argv.publicssl) { 147 | server.key = fs.readFileSync('key.pem'); 148 | server.cert = fs.readFileSync('cert.pem'); 149 | console.log('TacMap development server running publicly. Connect to https://localhost:%d/', server.address().port); 150 | } 151 | else { 152 | console.log('TacMap development server running locally. Connect to http://localhost:%d/', server.address().port); 153 | } 154 | }); 155 | server.on('error', function (e) { 156 | if (e.code === 'EADDRINUSE') { 157 | console.log('Error: Port %d is already in use, select a different port.', argv.port); 158 | console.log('Example: node server.js --port %d', argv.port + 1); 159 | } 160 | else if (e.code === 'EACCES') { 161 | console.log('Error: This process does not have permission to listen on port %d.', argv.port); 162 | if (argv.port < 1024) { 163 | console.log('Try a port number higher than 1024.'); 164 | } 165 | } 166 | console.log(e); 167 | process.exit(1); 168 | }); 169 | server.on('close', function () { 170 | console.log('TacMap server stopped.'); 171 | }); 172 | process.on('SIGINT', function () { 173 | server.close(function () { 174 | process.exit(0); 175 | }); 176 | }); 177 | app.get('/', function (req, res) { 178 | res.sendFile(__dirname + '/public/server.html'); 179 | }); 180 | 181 | app.get('/node_modules/*', function (req, res) { 182 | res.sendFile(__dirname + '/' + req.url); 183 | }); 184 | 185 | app.get('/server', function (req, res) { 186 | res.sendFile(__dirname + '/public/server.html'); 187 | }); 188 | 189 | app.get('/unit', function (req, res) { 190 | res.sendFile(__dirname + '/public/unit.html'); 191 | }); 192 | 193 | app.get('/json/*', function (req, res) { 194 | res.sendFile(__dirname + '/public' + req.url); 195 | }); 196 | 197 | app.post('/json/*', function (req, res) { 198 | fs.writeFile(__dirname + '/public' + req.url, JSON.stringify(req.body), function () { 199 | res.end(); 200 | }); 201 | }); 202 | 203 | app.put('/json/*', function (req, res) { 204 | fs.writeFile(__dirname + '/public' + req.url, JSON.stringify(req.body), function () { 205 | res.end(); 206 | }); 207 | }); 208 | 209 | app.put('/xml/*', function (req, res) { 210 | console.log("Put " + req.url); 211 | console.log(req.body); 212 | fs.writeFile(__dirname + '/public' + req.url, req.body, function () { 213 | res.end(); 214 | }); 215 | }); 216 | 217 | app.post('/entity/*'), 218 | function (req, res) { 219 | console.log("Post entity " + req.url); 220 | console.log(req.body); 221 | fs.writeFile(__dirname + '/public' + req.url, req.body, function () { 222 | res.end(); 223 | }); 224 | } 225 | 226 | var io = require('socket.io')(server); 227 | var missionid = "Default"; 228 | var missiondata = []; 229 | var servers = []; 230 | var units = []; 231 | var allconnections = []; 232 | 233 | io.on('connection', function (socket) { 234 | 235 | allconnections.push(socket); 236 | 237 | socket.on('socketDisconnect', function () { 238 | var i = allconnections.indexOf(socket); 239 | console.log(i.id + "Socjket Disconnected"); 240 | delete allconnections[i]; 241 | }); 242 | // Use socket to communicate with this particular unit only, sending it it's own id 243 | socket.emit('connection', { 244 | message: 'Msg Socket Ready', 245 | socketid: socket.id 246 | }); 247 | socket.on('server connected', function (data) { 248 | console.log("server connect to socket: " + data.socketid + ", mission:" + data.missionid); 249 | servers.push({ 250 | server: data.socketid 251 | }); 252 | if (missionid === "Default") { 253 | missiondata = data.missiondata; 254 | io.emit('init server', { 255 | target: "server", 256 | missionid: data.missionid, 257 | missiondata: missiondata 258 | }); 259 | } 260 | else { 261 | io.emit('init server', { 262 | target: "server", 263 | missionid: missionid, 264 | missiondata: missiondata 265 | }); 266 | } 267 | }); 268 | socket.on('unit connected', function (data) { 269 | console.log("units connect: " + data.id + " set mission: " + missionid); 270 | units.push({ 271 | unit: data.id 272 | }); 273 | io.emit('unit connected', { 274 | missionid: missionid, 275 | missiondata: missiondata 276 | }); 277 | }); 278 | socket.on('send msg', function (data) { 279 | console.log('send msg from ' + data.message.unit + ' to ' + data.message.net); 280 | io.emit('msg sent', data); 281 | }); 282 | socket.on('unit join', function (data) { 283 | //console.log(data.unitid + ' joined ' + data.netname); 284 | socket.join(data.netname); 285 | io.emit('unit joined', { 286 | unitid: data.unitid, 287 | netname: data.netname 288 | }); 289 | }); 290 | socket.on('server join', function (data) { 291 | //console.log(data.serverid + ' joined ' + data.netname); 292 | socket.join(data.netname); 293 | io.emit('server joined', { 294 | serverid: data.serverid, 295 | netname: data.netname 296 | }); 297 | }); 298 | socket.on('server leave', function (data) { 299 | // console.log(data.serverid + ' left ' + data.netname); 300 | socket.leave(data.netname); 301 | io.emit('server left', { 302 | serverid: data.serverid, 303 | netname: data.netname 304 | }); 305 | }); 306 | socket.on('unit leave', function (data) { 307 | //console.log(data.unitid + ' left ' + data.netname); 308 | socket.leave(data.netname); 309 | io.emit('unit left', { 310 | unitid: data.unitid, 311 | netname: data.netname 312 | }); 313 | }); 314 | socket.on('add entity', function (data) { 315 | console.log("emit add entity: " + data._id); 316 | io.emit('add entity', data); 317 | }); 318 | socket.on('set mission', function (data) { 319 | console.log("set mission: " + data.missionid); 320 | missionid = data.missionid; 321 | missiondata = data.missiondata; 322 | io.emit('set mission', { 323 | target: "unit", 324 | missionid: missionid, 325 | missiondata: missiondata 326 | }); 327 | }); 328 | socket.on('mission running', function () { 329 | missionRunning = true; 330 | io.emit('start mission'); 331 | }); 332 | socket.on('mission stopped', function () { 333 | missionRunning = false; 334 | io.emit('stop mission'); 335 | }); 336 | socket.on('mission time', function (data) { 337 | io.emit('set time', data); 338 | }); 339 | }); 340 | })(); -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------