Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
Executable
+273
@@ -0,0 +1,273 @@
|
||||
# Leaflet map for displaying mounting points
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Usage](#usage)
|
||||
2. [Programming](#programming)
|
||||
1. [Basics](#basics)
|
||||
1. [TypeScript](#typescript)
|
||||
2. [AngularJs](#angularjs)
|
||||
3. [Leaflet](#leaflet)
|
||||
2. [The code](#the-code)
|
||||
|
||||
_Note: view this in a program that supports markdown_
|
||||
_(e.g. VS Code or a browser with plugin) and it will look much better_
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Connection
|
||||
|
||||
When opening index.html you will first see the connection menu.
|
||||
Here you can choose if you want to connect to localhost:2080, enter
|
||||
a custom ip or load data from the browser cache.
|
||||
|
||||
### Main page
|
||||
|
||||
The main page consists of two parts: the map on the left side and the selection
|
||||
menu on the right side.
|
||||
The map will display the buildings and mouting points received from the previously
|
||||
entered websocket. Buildings are currently light blue while mounting points are
|
||||
highlighted with a typical map marker in black. These markers will open a popup
|
||||
that displays additional info when clicked.
|
||||
The selection menu on the right side has three sections, namely "Layers", "Asset
|
||||
types" and finally "Selected mounting points".
|
||||
|
||||
- The layer selection is handled via checkboxes where checked means displayed
|
||||
- The asset type selection happens with a dropdown menu
|
||||
- Selected mounting points are displayed in a table with tag/designation and id
|
||||
|
||||
The table displaying selected mounting points also features a button for each
|
||||
point that centers the map on it and opens its popup. It can also be searched
|
||||
using the text input above it (case sensitive, checks if name or id contain given
|
||||
text).
|
||||
|
||||
---
|
||||
|
||||
## Programming
|
||||
|
||||
### Basics
|
||||
|
||||
This program is a kind of messy combination of several libraries/frameworks that
|
||||
weren't really made to work with each other soI have to explain some things first:
|
||||
|
||||
#### TypeScript
|
||||
|
||||
TypeScript (TS) is a superset of JavaScript (JS) which means every JS command is also valid TS.
|
||||
The big difference is that TS is typed (who would have guessed?) andfeatures object
|
||||
oriented programming. Concepts such as classes and inheritance are
|
||||
supported. TS is transpiled into JS. This secures compatibility with most browsers.
|
||||
|
||||
A little syntax example of TS:
|
||||
|
||||
```typescript
|
||||
var name: type = value;
|
||||
|
||||
function name(parameter: type): returnType {
|
||||
// ...
|
||||
}
|
||||
|
||||
class name extends inheritedClass {
|
||||
PropertyOne: type;
|
||||
PropertyTwo: type = value;
|
||||
|
||||
constructor(parameter: type) {
|
||||
this.PropertyOne = parameter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The command `tsc --build <file.ts>` can be used to transpile "file.ts",
|
||||
`tsc --watch <file.ts>` however will stay running and always retranspile when
|
||||
it detects changes in "file.ts".
|
||||
|
||||
#### AngularJs
|
||||
|
||||
AngularJs is a kind of library for JS/htm which allows you to build dynamic pages
|
||||
with really little actual code. There has to be some backing code but most of it
|
||||
will actually happen wihtin the html file. It's kinda hard to explain but here's
|
||||
an example:
|
||||
|
||||
```html
|
||||
<li>
|
||||
<ul ng-repeat="x in [1, 2, 3, 4, 5]">
|
||||
{{x}}
|
||||
</ul>
|
||||
</li>
|
||||
```
|
||||
|
||||
This example doesn't really show anything too useful but you can see two of the most
|
||||
important concepts of AngularJs: directives and templates.
|
||||
|
||||
- The directive `ng-repeat` for example will repeat the tag it is in dynamically
|
||||
depending on its content, in this case 5 times
|
||||
- The template `{{x}}` will be dynamically replaced by the value of x, in this
|
||||
case 1 thorugh 5
|
||||
|
||||
When viewed in a browser the example will render as this:
|
||||
|
||||
> - 1
|
||||
> - 2
|
||||
> - 3
|
||||
> - 4
|
||||
> - 5
|
||||
|
||||
We can make JS variables visible to AngularJs by attaching them to its `$scope`
|
||||
object.
|
||||
|
||||
This was really short but I can't really teach a whole framework within a
|
||||
readme. There is a nice (German) [tutorial](https://angular.de/artikel/angularjs-tutorial-deutsch/)
|
||||
though.
|
||||
|
||||
There actually is a newer version of Angular explicitly for TS but it's way more
|
||||
complicated so I stuck with the old JS version. It was a headache to get that to
|
||||
work but now it does. Don't touch it.
|
||||
|
||||
#### Leaflet
|
||||
|
||||
Leaflet is a JS library for creating maps. It's rather simple to understand
|
||||
and has nice [documentation](https://leafletjs.com/reference-1.4.0.html).
|
||||
Basically you create a div with a special id and then put stuff like markers
|
||||
into it via JS. Not much I could explain here. Except maybe I can say a sentence
|
||||
about function chaining. Leaflet smartly uses return types in its functions that
|
||||
allow you to chain them like crazy. For example:
|
||||
|
||||
```typescript
|
||||
var marker: L.Marker = L.marker(coordinates)
|
||||
.bindPopup(popupContent)
|
||||
.addTo(map);
|
||||
```
|
||||
|
||||
`.bindPopup()` and `.addTo()` both still return the marker. This way you can get
|
||||
a lot of stuff done with a single statement.
|
||||
In this example you can also see that Leaflet likes to use factory methods instead
|
||||
of instance contructors (`new`). Feels weird in the beginning but makes sense when
|
||||
you keep in mind that this is a TS port of a JS library.
|
||||
|
||||
### The code
|
||||
|
||||
Let me first tell you how to actually run the code:
|
||||
|
||||
1. Transpile: Ctrl+Shift+B in VS Code, choose "build"
|
||||
2. Browserify: `browserify main.js -o scripts/main.js`
|
||||
3. Open index.html
|
||||
|
||||
The browserify step is needed since this program is basically a nodejs app.
|
||||
Browserify combines all node modules into a single .js file
|
||||
so we can actually use the file in a html website.
|
||||
|
||||
To clear things up:
|
||||
|
||||
- ./main.ts is the source file - _develop here_
|
||||
- ./main.js is the transpiled file - _don't touch it_
|
||||
- ./scripts/main.js is the final browerified file - _don't even look at it_
|
||||
|
||||
When developing it is way easier to have those commands in watch mode so
|
||||
you can simply refresh the browser to see the changes you made:
|
||||
|
||||
- Ctrl+Shift+B in VS Code, choose watch
|
||||
- `watchify main.js -o scripts/main.js`
|
||||
|
||||
These commands will automatically retranspile and rebrowserify main.ts when you
|
||||
hit save in an editor of your choice (use VS Code though - it's the best).
|
||||
|
||||
tsc, browserify and watchify can be installed via npm.
|
||||
|
||||
**Also, don't update utm-latlng!**
|
||||
utm-latlng is a dependency which features conversion of coordinates from
|
||||
latitude/longtitude to UTM and vice-versa. There's a bug in it that prevents
|
||||
the correct conversion from UTM back to lat/lon in the current version. There's
|
||||
a pull request with a one line fix on GitHub but that was half a year ago and
|
||||
has never been merged so I copied the line and pasted it in the local installation
|
||||
of the library. Updating it will probably break it. So don't.
|
||||
_Actually, it's probably best to not update anything, just to be sure._
|
||||
|
||||
---
|
||||
|
||||
Now let's get to the actual code. I wont explain every little detail since there
|
||||
are additional comments in the code but I will highlight _some_ things. For writing
|
||||
I recommend VS Code.
|
||||
|
||||
- The only files needed to display the final page in the end are:
|
||||
- index.html
|
||||
- style.css
|
||||
- The scripts folder and its content
|
||||
- the img forler and its content
|
||||
- index.html is rather simple. Contains additional comments as well but here's
|
||||
some more things:
|
||||
- leaflet.css has to be before leaflet.min.js apparently
|
||||
- Everything withing `{{}}` is an AngularJs template and will be filled
|
||||
dynamically with the code inside
|
||||
- `ng-click` is basically `onclick`
|
||||
- I wont even talk about style.css
|
||||
- main.ts. This is the evil one
|
||||
- Don't question the angular module and controllers. They are basically magic.
|
||||
- `module Calc` is a little collection of some math stuff:
|
||||
- `RadToDeg()` and `DegToRad()` are really just one-liner functions to
|
||||
convert between degrees and radians
|
||||
- `RotatePoint()` rotates a point around an angle using fancy trigonometry
|
||||
I snatched of the internet
|
||||
- class UTM` is just there so a have a uniform way of representing UTM
|
||||
coords
|
||||
- `LatLonToUtm()` and `UtmToLatLon()` are just wrappers to simplify the
|
||||
respective utm-latlng functions
|
||||
- `class MountingPoint` is a leaflet marker which stores additional info
|
||||
about the mounting point it represents
|
||||
- `class Layer` stores the markers and is basically a representation of
|
||||
floors but floors with the same name are stored in the same layer
|
||||
- `IsStringEmpty()` returns `true` when the given string is either empty,
|
||||
`null` or `undefined`
|
||||
- Any property of `$scope` within the angular controller is visible to
|
||||
angular templates within index.html
|
||||
- `$scope.loadData()` gets the data from the entered websocket and calls
|
||||
the event `webSocketResponse`
|
||||
- `$scope.$on("webSocketResponse", /* ... */)`:
|
||||
This handles the `webSocketResponse`-event. Almost the whole program is
|
||||
contained wihtin its callback. This is so that nothing tries to access
|
||||
any data that has not been received yet. It is divided into sections
|
||||
that handle the different types of objects we want to display, namely
|
||||
buildings, levels, mounting points and assets. It also sets up the leaflet
|
||||
map.
|
||||
- Nested `for (let x of y)`-"loops" are godsent. I mostly use them to get two
|
||||
objects with matching properties and then do stuff with them. For example:
|
||||
|
||||
```typescript
|
||||
for (let m of mountingPoints) {
|
||||
for (let b of buildings) {
|
||||
if (m.objectId == b.id) {
|
||||
// ...
|
||||
```
|
||||
|
||||
- `(layer.LLayer.getLayers() as unknown[]) as MountingPoint[])`
|
||||
This thing... Let me break it down.
|
||||
`layer` is an instance of my `Layer`-class. `LLayer` is the leaflet map
|
||||
layer it encapsulates.
|
||||
`.getLayers()` returns the array of markers on that layer. The problem
|
||||
I faced here was that they lost the properties I added in my
|
||||
`MountingPoint`-class because it returns `L.Marker`s.
|
||||
More precisely, the values is still there but not accessible since
|
||||
`L.Marker` doesn't have the properties.
|
||||
But I can't directly cast from `L.Marker` to `MountingPoint`.
|
||||
Therefore I do a cheaty cast via `unknown`.
|
||||
It's confusing as heck but the only way I can easily get access to the
|
||||
`MountingPoint`-properties.
|
||||
- The mean conditional statement at `foundBySearch` within
|
||||
`$scope.MountingPointFilter()` is a short way of saying
|
||||
`true` if either
|
||||
- `$scope.mpSearch` (search text input in index.html) is empty
|
||||
- currently tested mounting point tag/designation or id contain
|
||||
`$scope.mpSearch`
|
||||
- `$scope.SelectionChanged()` is called in the end so that I don't have
|
||||
to manually fix all the default selections
|
||||
- `$scope.$apply()` will update the `$scope`-object so that it's values
|
||||
are actually available in the angular templates within index.html.
|
||||
It would happen automatically eventually but I want to be on the safe side.
|
||||
Sometimes it is nessecary to call it, sometimes it thorws an error. Just try
|
||||
it out. The error doesn't seem to matter.
|
||||
|
||||
---
|
||||
|
||||
I hope this and the comments in the code are enough to understand it.
|
||||
If not, you can send me a [mail](mailto:justus.hertel@rheinmetall.com).
|
||||
If you are lucky I might remember how it works :p
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
Executable
+194
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="main">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Leaflet Stylesheet -->
|
||||
<link rel="stylesheet" type="text/css" href="scripts/leaflet.css" />
|
||||
|
||||
<!-- Wikitude, only in mobile app -->
|
||||
<script src="architect://architect.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Main Script (includes angular, leaflet and utm-latlng) -->
|
||||
<script src="scripts/main.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||
</head>
|
||||
<body ng-controller="ctrl">
|
||||
<div id="parentContainer" ng-hide="arMode">
|
||||
<!-- Contains everything, needed for css -->
|
||||
|
||||
<div id="connectionConatiner" ng-hide="connected">
|
||||
<!-- Contains the connection menu, only shown in the beginning -->
|
||||
<h1>
|
||||
Connection
|
||||
</h1>
|
||||
<button
|
||||
ng-click="loadDataFromWS('ws://localhost:2080')"
|
||||
ng-disabled="disableInput"
|
||||
style="width: 100%"
|
||||
>
|
||||
Connect to localhost
|
||||
</button>
|
||||
<br class="bigBr" />
|
||||
OR
|
||||
<br class="bigBr" />
|
||||
<button
|
||||
ng-click="loadDataFromWS(websocketIp)"
|
||||
ng-disabled="disableInput"
|
||||
>
|
||||
Connect to
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
ng-model="websocketIp"
|
||||
placeholder="ws://192.168.133.140:2080"
|
||||
ng-disabled="disableInput"
|
||||
/>
|
||||
<br class="bigBr" />
|
||||
OR
|
||||
<br class="bigBr" />
|
||||
<button
|
||||
ng-click="loadDataFromCache()"
|
||||
ng-disabled="disableInput"
|
||||
style="width: 100%"
|
||||
>
|
||||
Load cached data
|
||||
</button>
|
||||
<hr />
|
||||
<label>
|
||||
{{ status }}
|
||||
</label>
|
||||
<progress
|
||||
ng-show="disableInput"
|
||||
max="6"
|
||||
value="0"
|
||||
id="progressBar"
|
||||
>
|
||||
</progress>
|
||||
</div>
|
||||
|
||||
<div id="mapContainer">
|
||||
<!-- Contains the leaflet map, needed for css -->
|
||||
<div id="map">
|
||||
<!-- This is the actual map, see main.ts -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="guiContainer" ng-hide="loading">
|
||||
<!-- Contains the selection menu,
|
||||
disabled when the websocket response is being processed -->
|
||||
|
||||
<div ng-show="connected">
|
||||
<h1>
|
||||
Layers
|
||||
</h1>
|
||||
<table>
|
||||
<tr ng-repeat="layer in layers">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
ng-model="layer.Selected"
|
||||
ng-change="SelectionChanged()"
|
||||
/>
|
||||
{{ layer.Name }}
|
||||
</td>
|
||||
<!-- Creates table rows with a checkbox for every layer -->
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>
|
||||
Asset type
|
||||
</h1>
|
||||
<select
|
||||
ng-options="asset as asset.designation for asset in assets.availableAssets"
|
||||
ng-model="assets.selectedAsset"
|
||||
ng-change="SelectionChanged()"
|
||||
>
|
||||
</select>
|
||||
<!-- Creates a dropdown menu with all available asset types -->
|
||||
<hr />
|
||||
|
||||
<h1>
|
||||
Selected mounting points:
|
||||
{{
|
||||
(
|
||||
mountingPoints
|
||||
| filter: MountingPointFilter(mountingPoint)
|
||||
).length
|
||||
}}
|
||||
<!-- Displays the amount of mounting points that
|
||||
fit the selected criteria -->
|
||||
</h1>
|
||||
<input
|
||||
type="text"
|
||||
ng-model="mpSearch"
|
||||
placeholder="Search... (case sensitive)"
|
||||
/>
|
||||
<!-- The table below will be filtered according to the entered text,
|
||||
see main.ts -->
|
||||
<button ng-click="mpSearch = ''">Clear</button>
|
||||
<!-- This just clears the text input above -->
|
||||
<br />
|
||||
<br />
|
||||
<div id="mp">
|
||||
<table
|
||||
ng-show="
|
||||
(
|
||||
mountingPoints
|
||||
| filter: MountingPointFilter(mountingPoint)
|
||||
).length > 0
|
||||
"
|
||||
>
|
||||
<!-- Enables the table if theres more than 0 selected mounting points -->
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
ID
|
||||
</th>
|
||||
<th>
|
||||
Position
|
||||
</th>
|
||||
</tr>
|
||||
<tr
|
||||
ng-repeat="mountingPoint in mountingPoints | filter: MountingPointFilter(mountingPoint)"
|
||||
>
|
||||
<!-- Creates table rows for each selected mounting point -->
|
||||
<td>
|
||||
{{
|
||||
mountingPoint.tag != ""
|
||||
? mountingPoint.tag
|
||||
: mountingPoint.designation
|
||||
}}
|
||||
</td>
|
||||
<!-- Displays mounting point tag if available,
|
||||
otherwise designation is displayed -->
|
||||
<td>
|
||||
{{ mountingPoint.id }}
|
||||
</td>
|
||||
<td>
|
||||
<button ng-click="ShowMp(mountingPoint)">
|
||||
Show
|
||||
</button>
|
||||
<!-- Opens the marker popup of the according mounting point,
|
||||
and centers the map on it, see main.ts -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="arMode" id="arCtrl">
|
||||
{{ arMountingPoint }}
|
||||
<br />
|
||||
<button onclick="window.Functions.BackFromAr()">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+503
@@ -0,0 +1,503 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// #region Initialization
|
||||
console.log("\nBegin initialization ##################################################");
|
||||
var angular_1 = __importDefault(require("angular"));
|
||||
angular_1.default != undefined
|
||||
? console.log("\tLoaded AngularJs.")
|
||||
: console.error("\tFailed to load AngularJs.");
|
||||
var leaflet_1 = __importDefault(require("leaflet"));
|
||||
leaflet_1.default != undefined
|
||||
? console.log("\tLoaded Leaflet.")
|
||||
: console.error("\tFailed to load Leaflet.");
|
||||
var utm_latlng_1 = __importDefault(require("utm-latlng"));
|
||||
utm_latlng_1.default != undefined
|
||||
? console.log("\tLoaded utm-latlng.")
|
||||
: console.error("\tFailed to load utm-latlng.");
|
||||
var isMobile = /Android/i.test(navigator.userAgent); // Check for mobile browser
|
||||
console.log("\tDetected " + (isMobile ? "mobile browser." : "desktop browser, disabling AR features."));
|
||||
// AngularJS
|
||||
var ngApp = angular_1.default.module("main", []);
|
||||
console.log("\tInitialized AngularJs.");
|
||||
console.log("Initialization finished ###############################################");
|
||||
// #endregion
|
||||
// #region Helpers
|
||||
var Calc;
|
||||
(function (Calc) {
|
||||
// Converts radians to degrees
|
||||
function RadToDeg(rad) {
|
||||
return rad * (180 / Math.PI);
|
||||
}
|
||||
Calc.RadToDeg = RadToDeg;
|
||||
// Converts degrees to radians
|
||||
function DegToRad(deg) {
|
||||
return (deg * Math.PI) / 180;
|
||||
}
|
||||
Calc.DegToRad = DegToRad;
|
||||
// Rotates a point around 0,0 by an angle in radians
|
||||
function RotatePoint(point, angle) {
|
||||
return {
|
||||
x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
|
||||
y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
|
||||
};
|
||||
}
|
||||
Calc.RotatePoint = RotatePoint;
|
||||
// UTM Coordinates
|
||||
var UTM = /** @class */ (function () {
|
||||
function UTM(e, n, zn, zl) {
|
||||
this.Easting = e;
|
||||
this.Northing = n;
|
||||
this.ZoneNumber = zn;
|
||||
this.ZoneLetter = zl;
|
||||
}
|
||||
return UTM;
|
||||
}());
|
||||
var _precision = 7; // Precision for LatLon to UTM conversion
|
||||
var _utm = new utm_latlng_1.default("WGS 84"); // Create UTM/LatLng converter for WGS84 projection
|
||||
// Wrapper for LatLon to UTM conversion
|
||||
function LatLonToUtm(lat, lon) {
|
||||
return _utm.convertLatLngToUtm(lat, lon, _precision);
|
||||
}
|
||||
Calc.LatLonToUtm = LatLonToUtm;
|
||||
// Wrapper for UTM to LatLon conversion
|
||||
function UtmToLatLon(utm) {
|
||||
var temp = _utm.convertUtmToLatLng(utm.Easting, utm.Northing, utm.ZoneNumber, utm.ZoneLetter);
|
||||
return { lat: temp.lat, lon: temp.lng };
|
||||
}
|
||||
Calc.UtmToLatLon = UtmToLatLon;
|
||||
})(Calc || (Calc = {}));
|
||||
// Leaflet marker with additional info to represent a mounting point
|
||||
var MountingPoint = /** @class */ (function (_super) {
|
||||
__extends(MountingPoint, _super);
|
||||
function MountingPoint() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this.SupportedAssets = [];
|
||||
_this.ID = -1;
|
||||
return _this;
|
||||
}
|
||||
return MountingPoint;
|
||||
}(leaflet_1.default.Marker));
|
||||
// Represents all levels with the same designation,
|
||||
// Stores the mounting points of those levels
|
||||
var Layer = /** @class */ (function () {
|
||||
function Layer(name) {
|
||||
this.Name = name;
|
||||
this.Ids = [];
|
||||
this.Selected = true;
|
||||
this.LLayer = leaflet_1.default.layerGroup(undefined);
|
||||
}
|
||||
return Layer;
|
||||
}());
|
||||
// Returns true if str is undefined, empty or null
|
||||
function IsStringEmpty(str) {
|
||||
return str == undefined || str == "" || str == null;
|
||||
}
|
||||
// jQuery like shortcut to get html elements
|
||||
function $(query) {
|
||||
return document.querySelector(query);
|
||||
}
|
||||
// Retrieves the angular $scope from outside a controller
|
||||
function GetScope() {
|
||||
return angular_1.default.element($("body")).scope();
|
||||
}
|
||||
function Status(str) {
|
||||
GetScope().status = str;
|
||||
console.log(str);
|
||||
$("#progressBar").value++;
|
||||
}
|
||||
// #endregion
|
||||
// Main AngularJs controller
|
||||
ngApp.controller("ctrl", function ($scope) {
|
||||
var data = {}; // Will hold the websocket response
|
||||
var t0 = null; // Measures the time it takes to load and parse
|
||||
$scope.disableInput = false; // True when loadData kicks in, disables the inputs
|
||||
$scope.connected = false; // True when websocket response was received
|
||||
$scope.status = undefined; // String that will be shown while connecting
|
||||
// Writes the websocket response to data when the load button is clicked
|
||||
/** @function */ $scope.loadDataFromWS = function (ip) {
|
||||
$scope.disableInput = true;
|
||||
console.log("Begin loading data ####################################################");
|
||||
t0 = performance.now(); // Get start time
|
||||
// Default value for ip if nothing was entered
|
||||
ip = IsStringEmpty(ip) ? "ws://192.168.133.140:2080" : ip;
|
||||
// Catch invalid ips
|
||||
try {
|
||||
var ws = new WebSocket(ip); // Connect to the websocket
|
||||
}
|
||||
catch (e) {
|
||||
alert("Invalid IP");
|
||||
return;
|
||||
}
|
||||
Status("\tConnecting to " + ip + "...");
|
||||
// Connect to the web socket
|
||||
ws.onopen = function () {
|
||||
console.log("\tConnected.");
|
||||
ws.send(JSON.stringify({
|
||||
type: "getEssentials",
|
||||
uuid: "UUID"
|
||||
}));
|
||||
};
|
||||
// Handles the response of the websocket
|
||||
ws.onmessage = function (response) {
|
||||
console.log("\tFetched " + Math.round(new TextEncoder().encode(response.data).length / 1000) + "kb.");
|
||||
data = JSON.parse(response.data).data; // Parse websocket response to data
|
||||
window.localStorage.setItem("data", escape(response.data));
|
||||
$scope.$broadcast("dataLoaded"); // Call response event to kick in the map and selection GUI
|
||||
};
|
||||
// Handle bad websocket responses
|
||||
ws.onclose = function (event) {
|
||||
if (event.code > 1000) {
|
||||
t0 = null;
|
||||
$scope.status = "";
|
||||
alert("Error when getting websocket response.\n" + event.code);
|
||||
$scope.disableInput = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
/** @function */ $scope.loadDataFromCache = function () {
|
||||
$scope.disableInput = true;
|
||||
console.log("Begin loading data ####################################################");
|
||||
t0 = performance.now();
|
||||
Status("Loading data from cache...");
|
||||
try {
|
||||
if (window.localStorage.getItem("data") == null)
|
||||
throw new Error();
|
||||
var _data = unescape(window.localStorage.getItem("data"));
|
||||
data = JSON.parse(_data).data;
|
||||
console.log("\tLoaded " + Math.round(new TextEncoder().encode(_data).length / 1000) + "kb from cache.");
|
||||
$scope.$broadcast("dataLoaded");
|
||||
}
|
||||
catch (_a) {
|
||||
$scope.status = "";
|
||||
$scope.disableInput = false;
|
||||
t0 = null;
|
||||
console.log("\tCould not load data from cache.");
|
||||
alert("No data cached.");
|
||||
}
|
||||
};
|
||||
// Handles websocket response event
|
||||
$scope.$on("dataLoaded", function () {
|
||||
var _data = data;
|
||||
$scope.connected = true; // Switches the connection menu for the mounting point selection
|
||||
$scope.loading = true; // Disables the GUI while doing stuff
|
||||
// #region MAP
|
||||
console.log("\tCreating map.");
|
||||
// Create the map, set view and image
|
||||
var map = new leaflet_1.default.Map("map").setView(new leaflet_1.default.LatLng(53.0528064472226, 8.89412999153137), 8);
|
||||
var imageBounds = new leaflet_1.default.LatLngBounds([
|
||||
[53.0528064472226, 8.89412999153137],
|
||||
[53.056520842139, 8.90030980110168]
|
||||
]);
|
||||
var mapImage = leaflet_1.default.imageOverlay("img/RDE2.jpg", imageBounds);
|
||||
map.addLayer(mapImage);
|
||||
map.fitBounds(imageBounds);
|
||||
// #endregion
|
||||
// #region BUILDINGS
|
||||
// Get buildings
|
||||
var buildings = _data.objects;
|
||||
Status("\tParsing " + buildings.length + " buildings...");
|
||||
// Create GeoJson from buidling footprints
|
||||
var footprintPolygons = {
|
||||
type: "FeatureCollection",
|
||||
features: []
|
||||
};
|
||||
for (var _i = 0, buildings_1 = buildings; _i < buildings_1.length; _i++) {
|
||||
var currentBuilding = buildings_1[_i];
|
||||
// Check if current builing has footprint data, if not: skip
|
||||
if (!(currentBuilding.path[0] == undefined)) {
|
||||
// Holds footprint coordinates of the current building
|
||||
var footprintCoords = [[]];
|
||||
// Parse footprint coordinates from current building
|
||||
for (var _a = 0, _b = currentBuilding.path; _a < _b.length; _a++) {
|
||||
var pos = _b[_a];
|
||||
footprintCoords[0].push([
|
||||
// Convert from radians to degrees and add to the array
|
||||
Calc.RadToDeg(pos.lon),
|
||||
Calc.RadToDeg(pos.lat)
|
||||
]);
|
||||
}
|
||||
// Last point has to be the same as the first point so we add it again
|
||||
footprintCoords[0].push([
|
||||
Calc.RadToDeg(currentBuilding.path[0].lon),
|
||||
Calc.RadToDeg(currentBuilding.path[0].lat)
|
||||
]);
|
||||
// Create new GeoJson Feature with building footprint
|
||||
var currentFootprint = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: currentBuilding.designation
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: footprintCoords
|
||||
}
|
||||
};
|
||||
// Add GeoJson feature to feature collection
|
||||
footprintPolygons.features.push(currentFootprint);
|
||||
}
|
||||
}
|
||||
// Draw building footprints to map, add name popups
|
||||
var footprintGeoJson = leaflet_1.default.geoJSON(footprintPolygons, {
|
||||
onEachFeature: function (feature, layer) {
|
||||
layer.bindPopup("<h2>" + feature.properties.name + "</h2>");
|
||||
}
|
||||
}).addTo(map);
|
||||
// #endregion
|
||||
// #region LEVELS
|
||||
var levels = _data.levels; // Levels are the actual floors in the building
|
||||
var layers = []; // Layers are their representation on the map
|
||||
Status("\tParsing " + levels.length + " levels...");
|
||||
for (var _c = 0, levels_1 = levels; _c < levels_1.length; _c++) {
|
||||
var level = levels_1[_c];
|
||||
// Create a layer for each level
|
||||
var add = true;
|
||||
for (var _d = 0, layers_1 = layers; _d < layers_1.length; _d++) {
|
||||
var layer = layers_1[_d];
|
||||
// Levels with duplicate names will be on the same layer
|
||||
if (level.designation === layer.Name) {
|
||||
add = false;
|
||||
layer.Ids.push(level.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (add) {
|
||||
// Create new Layer if level is not a duplicate
|
||||
var newLayer = new Layer(level.designation);
|
||||
newLayer.Ids.push(level.id);
|
||||
layers.push(newLayer);
|
||||
}
|
||||
}
|
||||
var outdoorLayer = new Layer("outdoor"); // Create a layer for mounting points outside of buildings
|
||||
outdoorLayer.Ids = [-1];
|
||||
var footprintLayer = new Layer("buildings"); // Create a layer for the building footprint
|
||||
footprintLayer.LLayer = footprintGeoJson;
|
||||
layers.push(outdoorLayer); // Add outdoor and footprint layers
|
||||
layers.push(footprintLayer);
|
||||
$scope.layers = layers; // Add layers to the checkbox menu
|
||||
// #endregion
|
||||
// #region MOUNTING POINTS
|
||||
// Get mounting points
|
||||
var mountingPoints = _data.mountingPoints;
|
||||
Status("\tParsing " + mountingPoints.length + " mounting points...");
|
||||
for (var _e = 0, mountingPoints_1 = mountingPoints; _e < mountingPoints_1.length; _e++) {
|
||||
var m = mountingPoints_1[_e];
|
||||
for (var _f = 0, buildings_2 = buildings; _f < buildings_2.length; _f++) {
|
||||
var b = buildings_2[_f];
|
||||
if (b.id == m.objectId) {
|
||||
// Current mounting point is in current buidling?
|
||||
b.utmRefPoint = Calc.LatLonToUtm(Calc.RadToDeg(b.refPoint.lat), Calc.RadToDeg(b.refPoint.lon)); // Converts refPoint of the building to UTM
|
||||
m.fixedPosition = Calc.RotatePoint({ x: m.position3d.x, y: m.position3d.y }, Calc.DegToRad(b.rotationDegree)); // Rotates coords of the mounting point by rotationDegree of the building
|
||||
m.absoluteUtm = b.utmRefPoint;
|
||||
m.absoluteUtm.Easting += m.fixedPosition.x;
|
||||
m.absoluteUtm.Northing += m.fixedPosition.y;
|
||||
m.absoluteLatLon = Calc.UtmToLatLon(m.absoluteUtm);
|
||||
// Add refPoint to the relative position and convert back to lat/lng
|
||||
var arButtonHtml = isMobile
|
||||
? "<button\n\t\t\t\t\t\t\t\tonclick=\"window.Functions.DisplayInAr(" + m.id + ")\"\n\t\t\t\t\t\t\t\tid=\"arButton\"\n\t\t\t\t\t\t >\n\t\t\t\t\t\t\t\tShow in AR\n\t\t\t\t\t\t\t</button>"
|
||||
: "<i>AR not supported on desktop.</i>";
|
||||
for (var _g = 0, layers_2 = layers; _g < layers_2.length; _g++) {
|
||||
var l = layers_2[_g];
|
||||
for (var _h = 0, _j = l.Ids; _h < _j.length; _h++) {
|
||||
var i = _j[_h];
|
||||
if (i == m.levelId) {
|
||||
// Current mouting point is on current level?
|
||||
var marker = new MountingPoint(
|
||||
// Create a marker for the mounting point
|
||||
new leaflet_1.default.LatLng(m.absoluteLatLon.lat, m.absoluteLatLon.lon), {
|
||||
icon: leaflet_1.default.icon({
|
||||
iconUrl: "img/marker.png",
|
||||
iconSize: [30, 30]
|
||||
})
|
||||
})
|
||||
.bindPopup(
|
||||
// Create a popup with info about the mounting point
|
||||
"<h2>\n\t\t\t\t\t\t\t\t\t\t" + (IsStringEmpty(m.tag) ? m.designation : m.tag) + "\n\t\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t\t<table>\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>ID</td>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>" + m.id + "</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>Object ID</td>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>" + m.objectId + "</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>Supported Asset Type IDs</td>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>" + m.supportedAssetTypeIds + "</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t\t\t\t" + arButtonHtml + "\n\t\t\t\t\t\t\t\t\t\t")
|
||||
.addTo(map); // Add it to the map
|
||||
marker.SupportedAssets = // Add info about supported asset types
|
||||
m.supportedAssetTypeIds;
|
||||
marker.ID = m.id; // Add mounting point id
|
||||
l.LLayer.addLayer(marker); // Add marker to corresponding layer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$scope.mountingPoints = mountingPoints;
|
||||
// #endregion
|
||||
// #region ASSETS
|
||||
// Get asset types
|
||||
var assets = _data.assetTypes;
|
||||
Status("\tParsing " + assets.length + " assets...");
|
||||
// Add asset types to the dropdown menu
|
||||
$scope.assets = {
|
||||
availableAssets: assets,
|
||||
selectedAsset: assets[0]
|
||||
};
|
||||
// #endregion
|
||||
// #region ANGULAR FUNCTIONS
|
||||
// Called by either the layer or asset selection
|
||||
/** @function */ $scope.SelectionChanged = function () {
|
||||
var points = 0;
|
||||
for (var _i = 0, layers_3 = layers; _i < layers_3.length; _i++) {
|
||||
var layer = layers_3[_i];
|
||||
if (layer.Selected) {
|
||||
map.addLayer(layer.LLayer); // Add selected layers
|
||||
// Hacky type conversion to get the supported asset types from each mounting point on a layer
|
||||
// If it's stupid but it works it's not stupid
|
||||
for (var _a = 0, _b = layer.LLayer.getLayers(); _a < _b.length; _a++) {
|
||||
var m = _b[_a];
|
||||
try {
|
||||
for (var _c = 0, _d = m.SupportedAssets; _c < _d.length; _c++) {
|
||||
var a = _d[_c];
|
||||
if (a == $scope.assets.selectedAsset.id) {
|
||||
m.addTo(map); // Add markers for selected asset type
|
||||
++points;
|
||||
}
|
||||
else {
|
||||
m.removeFrom(map); // Remove all other markers
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (_e) {
|
||||
/** Sometimes theres an error here...
|
||||
* Doesn't seem to interfere with anything though and
|
||||
* I don't want to see it in the console
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
map.removeLayer(layer.LLayer); // Remove unselected layers
|
||||
}
|
||||
console.log("Selected " + points + " mounting point" + (points == 1 ? "" : "s") + ".");
|
||||
};
|
||||
// Filter for selected mounting point table and count
|
||||
/** @function */ $scope.MountingPointFilter = function () {
|
||||
return function (mp) {
|
||||
var correctAssetType = false;
|
||||
for (var _i = 0, _a = mp.supportedAssetTypeIds; _i < _a.length; _i++) {
|
||||
var i = _a[_i];
|
||||
if (i == $scope.assets.selectedAsset.id) {
|
||||
correctAssetType = true;
|
||||
break;
|
||||
}
|
||||
} // Current mounting point supports selected asset type
|
||||
var correctLayer = false;
|
||||
for (var _b = 0, layers_4 = layers; _b < layers_4.length; _b++) {
|
||||
var layer = layers_4[_b];
|
||||
for (var _c = 0, _d = layer.Ids; _c < _d.length; _c++) {
|
||||
var i = _d[_c];
|
||||
if (layer.Selected && i == mp.levelId) {
|
||||
correctLayer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // Current mounting point is on a selected layer
|
||||
var foundBySearch = IsStringEmpty($scope.mpSearch)
|
||||
? true // Automatically true if search input empty
|
||||
: (IsStringEmpty(mp.tag) ? mp.designation : mp.tag).indexOf($scope.mpSearch) >= 0 || mp.id.toString().indexOf($scope.mpSearch) >= 0;
|
||||
// true if tag, designation or id contain search input
|
||||
return correctAssetType && correctLayer && foundBySearch;
|
||||
};
|
||||
};
|
||||
// Function for the Show button
|
||||
/** @function */ $scope.ShowMp = function (mp) {
|
||||
for (var _i = 0, layers_5 = layers; _i < layers_5.length; _i++) {
|
||||
var l = layers_5[_i];
|
||||
// Not this again...
|
||||
for (var _a = 0, _b = l.LLayer.getLayers(); _a < _b.length; _a++) {
|
||||
var m = _b[_a];
|
||||
if (m.ID == mp.id) {
|
||||
// Find clicked mounting point
|
||||
m.openPopup(); // Open corresponding popup
|
||||
map.panTo(m.getLatLng()); // Center map on the popup
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// #endregion
|
||||
// Get final time and format it
|
||||
var time = t0 != null
|
||||
? "Finished loading data in " + (performance.now() -
|
||||
t0) + "ms ########################################"
|
||||
: "Finished loading data #################################################";
|
||||
console.log(time);
|
||||
$scope.SelectionChanged(); // Make initial map state fit the initial menu state
|
||||
$scope.loading = false; // Done loading
|
||||
$scope.$apply();
|
||||
});
|
||||
});
|
||||
// #region WIKITUDE FUNCTIONS
|
||||
var currentPOI = undefined; // Holds current wikitude ar marker
|
||||
// Shows an augmented reality marker vor the selected mounting point
|
||||
function _displayInAr(id) {
|
||||
var $scope = GetScope(); // Retrieve angular scope
|
||||
var mountingPoint;
|
||||
for (var _i = 0, _a = $scope.mountingPoints; _i < _a.length; _i++) {
|
||||
var mp = _a[_i];
|
||||
if (mp.id == id) {
|
||||
mountingPoint = mp;
|
||||
break;
|
||||
}
|
||||
} // Get correct mounting point
|
||||
var name = IsStringEmpty(mountingPoint.tag)
|
||||
? mountingPoint.designation
|
||||
: mountingPoint.tag;
|
||||
$scope.arMountingPoint = name;
|
||||
// Display mounting point tag/designation
|
||||
currentPOI = new AR.GeoObject([
|
||||
new AR.GeoLocation(mountingPoint.absoluteLatLon.lat, mountingPoint.absoluteLatLon.lon)
|
||||
], {
|
||||
drawables: {
|
||||
cam: [
|
||||
new AR.ImageDrawable(new AR.ImageResource("img/armarker.png"), 2.5, { zOrder: 0, opacity: 1 }),
|
||||
new AR.Label(name, 1, {
|
||||
zOrder: 1,
|
||||
style: { textColor: "#ffffff" }
|
||||
})
|
||||
],
|
||||
indicator: new AR.ImageDrawable(new AR.ImageResource("img/arindicator.png"), 0.1, {
|
||||
enabled: true,
|
||||
verticalAnchor: AR.CONST.VERTICAL_ANCHOR.TOP
|
||||
})
|
||||
}
|
||||
}); // Create AR marker
|
||||
$scope.arMode = true; // Hide map and menu
|
||||
$scope.$apply();
|
||||
console.log("Switched to AR mode.");
|
||||
}
|
||||
// Goes back to the map view
|
||||
function _backFromAr() {
|
||||
var $scope = GetScope(); // Retrieve angular scope
|
||||
$scope.arMountingPoint = ""; // Clear name display
|
||||
$scope.arMode = false; // Show map and menu again
|
||||
currentPOI.destroy();
|
||||
currentPOI = undefined;
|
||||
$scope.$apply();
|
||||
console.log("Switched to map mode.");
|
||||
}
|
||||
// Attach functions
|
||||
window.Functions = window.Functions || {
|
||||
DisplayInAr: _displayInAr,
|
||||
BackFromAr: _backFromAr
|
||||
};
|
||||
// #endregion
|
||||
// #endregion
|
||||
Executable
+664
@@ -0,0 +1,664 @@
|
||||
// #region Initialization
|
||||
console.log(
|
||||
"\nBegin initialization ##################################################"
|
||||
);
|
||||
|
||||
import angular from "angular";
|
||||
angular != undefined
|
||||
? console.log("\tLoaded AngularJs.")
|
||||
: console.error("\tFailed to load AngularJs.");
|
||||
|
||||
import L, { PopupEvent, Popup } from "leaflet";
|
||||
L != undefined
|
||||
? console.log("\tLoaded Leaflet.")
|
||||
: console.error("\tFailed to load Leaflet.");
|
||||
|
||||
import UTMLatLng from "utm-latlng";
|
||||
UTMLatLng != undefined
|
||||
? console.log("\tLoaded utm-latlng.")
|
||||
: console.error("\tFailed to load utm-latlng.");
|
||||
|
||||
const isMobile: boolean = /Android/i.test(navigator.userAgent); // Check for mobile browser
|
||||
console.log(
|
||||
`\tDetected ${
|
||||
isMobile ? "mobile browser." : "desktop browser, disabling AR features."
|
||||
}`
|
||||
);
|
||||
|
||||
// import AR from "scripts/architect"
|
||||
declare var AR: any; // Is loaded in html from scripts/architect.js
|
||||
|
||||
// AngularJS
|
||||
const ngApp = angular.module("main", []);
|
||||
console.log("\tInitialized AngularJs.");
|
||||
|
||||
console.log(
|
||||
"Initialization finished ###############################################"
|
||||
);
|
||||
// #endregion
|
||||
|
||||
// #region Helpers
|
||||
|
||||
module Calc {
|
||||
// Converts radians to degrees
|
||||
export function RadToDeg(rad: number): number {
|
||||
return rad * (180 / Math.PI);
|
||||
}
|
||||
|
||||
// Converts degrees to radians
|
||||
export function DegToRad(deg: number): number {
|
||||
return (deg * Math.PI) / 180;
|
||||
}
|
||||
|
||||
// Rotates a point around 0,0 by an angle in radians
|
||||
export function RotatePoint(
|
||||
point: { x: number; y: number },
|
||||
angle: number
|
||||
): { x: number; y: number } {
|
||||
return {
|
||||
x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
|
||||
y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
|
||||
};
|
||||
}
|
||||
|
||||
// UTM Coordinates
|
||||
class UTM {
|
||||
Easting: number;
|
||||
Northing: number;
|
||||
ZoneNumber: number;
|
||||
ZoneLetter: string;
|
||||
constructor(e: number, n: number, zn: number, zl: string) {
|
||||
this.Easting = e;
|
||||
this.Northing = n;
|
||||
this.ZoneNumber = zn;
|
||||
this.ZoneLetter = zl;
|
||||
}
|
||||
}
|
||||
|
||||
const _precision: number = 7; // Precision for LatLon to UTM conversion
|
||||
const _utm = new UTMLatLng("WGS 84"); // Create UTM/LatLng converter for WGS84 projection
|
||||
|
||||
// Wrapper for LatLon to UTM conversion
|
||||
export function LatLonToUtm(lat: number, lon: number) {
|
||||
return _utm.convertLatLngToUtm(lat, lon, _precision);
|
||||
}
|
||||
|
||||
// Wrapper for UTM to LatLon conversion
|
||||
export function UtmToLatLon(utm: UTM): { lat: number; lon: number } {
|
||||
let temp = _utm.convertUtmToLatLng(
|
||||
utm.Easting,
|
||||
utm.Northing,
|
||||
utm.ZoneNumber,
|
||||
utm.ZoneLetter
|
||||
);
|
||||
return { lat: temp.lat, lon: temp.lng };
|
||||
}
|
||||
}
|
||||
|
||||
// Leaflet marker with additional info to represent a mounting point
|
||||
class MountingPoint extends L.Marker {
|
||||
SupportedAssets: number[] = [];
|
||||
ID: number = -1;
|
||||
}
|
||||
|
||||
// Represents all levels with the same designation,
|
||||
// Stores the mounting points of those levels
|
||||
class Layer {
|
||||
Name: string;
|
||||
Ids: number[];
|
||||
Selected: boolean;
|
||||
LLayer: L.LayerGroup;
|
||||
|
||||
constructor(name) {
|
||||
this.Name = name;
|
||||
this.Ids = [];
|
||||
this.Selected = true;
|
||||
this.LLayer = L.layerGroup(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if str is undefined, empty or null
|
||||
function IsStringEmpty(str: string): boolean {
|
||||
return str == undefined || str == "" || str == null;
|
||||
}
|
||||
|
||||
// jQuery like shortcut to get html elements
|
||||
function $(query: string): HTMLElement | null {
|
||||
return document.querySelector(query);
|
||||
}
|
||||
|
||||
// Retrieves the angular $scope from outside a controller
|
||||
function GetScope(): any {
|
||||
return angular.element($("body") as Element).scope() as any;
|
||||
}
|
||||
|
||||
function Status(str: string): void {
|
||||
GetScope().status = str;
|
||||
console.log(str);
|
||||
($("#progressBar") as HTMLProgressElement).value++;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// Main AngularJs controller
|
||||
ngApp.controller("ctrl", $scope => {
|
||||
let data: any = {}; // Will hold the websocket response
|
||||
let t0: number | null = null; // Measures the time it takes to load and parse
|
||||
$scope.disableInput = false; // True when loadData kicks in, disables the inputs
|
||||
$scope.connected = false; // True when websocket response was received
|
||||
$scope.status = undefined; // String that will be shown while connecting
|
||||
|
||||
// Writes the websocket response to data when the load button is clicked
|
||||
/** @function */ $scope.loadDataFromWS = (ip: string): void => {
|
||||
$scope.disableInput = true;
|
||||
console.log(
|
||||
"Begin loading data ####################################################"
|
||||
);
|
||||
|
||||
t0 = performance.now(); // Get start time
|
||||
|
||||
// Default value for ip if nothing was entered
|
||||
ip = IsStringEmpty(ip) ? "ws://192.168.133.140:2080" : ip;
|
||||
|
||||
// Catch invalid ips
|
||||
try {
|
||||
var ws = new WebSocket(ip); // Connect to the websocket
|
||||
} catch (e) {
|
||||
alert("Invalid IP");
|
||||
return;
|
||||
}
|
||||
|
||||
Status(`\tConnecting to ${ip}...`);
|
||||
|
||||
// Connect to the web socket
|
||||
ws.onopen = () => {
|
||||
console.log("\tConnected.");
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "getEssentials",
|
||||
uuid: "UUID"
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Handles the response of the websocket
|
||||
ws.onmessage = response => {
|
||||
console.log(
|
||||
`\tFetched ${Math.round(
|
||||
new TextEncoder().encode(response.data).length / 1000
|
||||
)}kb.`
|
||||
);
|
||||
data = JSON.parse(response.data).data; // Parse websocket response to data
|
||||
window.localStorage.setItem("data", escape(response.data));
|
||||
$scope.$broadcast("dataLoaded"); // Call response event to kick in the map and selection GUI
|
||||
};
|
||||
|
||||
// Handle bad websocket responses
|
||||
ws.onclose = event => {
|
||||
if (event.code > 1000) {
|
||||
t0 = null;
|
||||
$scope.status = "";
|
||||
alert(`Error when getting websocket response.\n${event.code}`);
|
||||
$scope.disableInput = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** @function */ $scope.loadDataFromCache = () => {
|
||||
$scope.disableInput = true;
|
||||
console.log(
|
||||
"Begin loading data ####################################################"
|
||||
);
|
||||
t0 = performance.now();
|
||||
Status("Loading data from cache...");
|
||||
|
||||
try {
|
||||
if (window.localStorage.getItem("data") == null) throw new Error();
|
||||
let _data = unescape(window.localStorage.getItem("data") as string);
|
||||
data = JSON.parse(_data).data;
|
||||
console.log(
|
||||
`\tLoaded ${Math.round(
|
||||
new TextEncoder().encode(_data).length / 1000
|
||||
)}kb from cache.`
|
||||
);
|
||||
$scope.$broadcast("dataLoaded");
|
||||
} catch {
|
||||
$scope.status = "";
|
||||
$scope.disableInput = false;
|
||||
t0 = null;
|
||||
console.log("\tCould not load data from cache.");
|
||||
alert("No data cached.");
|
||||
}
|
||||
};
|
||||
|
||||
// Handles websocket response event
|
||||
$scope.$on("dataLoaded", () => {
|
||||
let _data: any = data;
|
||||
|
||||
$scope.connected = true; // Switches the connection menu for the mounting point selection
|
||||
$scope.loading = true; // Disables the GUI while doing stuff
|
||||
|
||||
// #region MAP
|
||||
|
||||
console.log("\tCreating map.");
|
||||
|
||||
// Create the map, set view and image
|
||||
let map = new L.Map("map").setView(
|
||||
new L.LatLng(53.0528064472226, 8.89412999153137),
|
||||
8
|
||||
);
|
||||
let imageBounds = new L.LatLngBounds([
|
||||
[53.0528064472226, 8.89412999153137],
|
||||
[53.056520842139, 8.90030980110168]
|
||||
]);
|
||||
let mapImage = L.imageOverlay("img/RDE2.jpg", imageBounds);
|
||||
map.addLayer(mapImage);
|
||||
map.fitBounds(imageBounds);
|
||||
|
||||
// #endregion
|
||||
// #region BUILDINGS
|
||||
|
||||
// Get buildings
|
||||
let buildings: any[] = _data.objects;
|
||||
|
||||
Status(`\tParsing ${buildings.length} buildings...`);
|
||||
|
||||
// Create GeoJson from buidling footprints
|
||||
let footprintPolygons: GeoJSON.FeatureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: []
|
||||
};
|
||||
|
||||
for (let currentBuilding of buildings) {
|
||||
// Check if current builing has footprint data, if not: skip
|
||||
if (!(currentBuilding.path[0] == undefined)) {
|
||||
// Holds footprint coordinates of the current building
|
||||
let footprintCoords: number[][][] = [[]];
|
||||
|
||||
// Parse footprint coordinates from current building
|
||||
for (let pos of currentBuilding.path) {
|
||||
footprintCoords[0].push([
|
||||
// Convert from radians to degrees and add to the array
|
||||
Calc.RadToDeg(pos.lon),
|
||||
Calc.RadToDeg(pos.lat)
|
||||
]);
|
||||
}
|
||||
|
||||
// Last point has to be the same as the first point so we add it again
|
||||
footprintCoords[0].push([
|
||||
Calc.RadToDeg(currentBuilding.path[0].lon),
|
||||
Calc.RadToDeg(currentBuilding.path[0].lat)
|
||||
]);
|
||||
|
||||
// Create new GeoJson Feature with building footprint
|
||||
let currentFootprint: GeoJSON.Feature = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: currentBuilding.designation
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: footprintCoords
|
||||
}
|
||||
};
|
||||
|
||||
// Add GeoJson feature to feature collection
|
||||
footprintPolygons.features.push(currentFootprint);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw building footprints to map, add name popups
|
||||
let footprintGeoJson = L.geoJSON(footprintPolygons, {
|
||||
onEachFeature: (feature, layer) => {
|
||||
layer.bindPopup(`<h2>${feature.properties.name}</h2>`);
|
||||
}
|
||||
}).addTo(map);
|
||||
|
||||
// #endregion
|
||||
// #region LEVELS
|
||||
|
||||
let levels: any[] = _data.levels; // Levels are the actual floors in the building
|
||||
let layers: Layer[] = []; // Layers are their representation on the map
|
||||
|
||||
Status(`\tParsing ${levels.length} levels...`);
|
||||
|
||||
for (let level of levels) {
|
||||
// Create a layer for each level
|
||||
|
||||
let add: boolean = true;
|
||||
for (let layer of layers) {
|
||||
// Levels with duplicate names will be on the same layer
|
||||
if (level.designation === layer.Name) {
|
||||
add = false;
|
||||
layer.Ids.push(level.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (add) {
|
||||
// Create new Layer if level is not a duplicate
|
||||
let newLayer: Layer = new Layer(level.designation);
|
||||
newLayer.Ids.push(level.id);
|
||||
layers.push(newLayer);
|
||||
}
|
||||
}
|
||||
|
||||
let outdoorLayer: Layer = new Layer("outdoor"); // Create a layer for mounting points outside of buildings
|
||||
outdoorLayer.Ids = [-1];
|
||||
|
||||
let footprintLayer: Layer = new Layer("buildings"); // Create a layer for the building footprint
|
||||
footprintLayer.LLayer = footprintGeoJson;
|
||||
|
||||
layers.push(outdoorLayer); // Add outdoor and footprint layers
|
||||
layers.push(footprintLayer);
|
||||
|
||||
$scope.layers = layers; // Add layers to the checkbox menu
|
||||
|
||||
// #endregion
|
||||
// #region MOUNTING POINTS
|
||||
|
||||
// Get mounting points
|
||||
let mountingPoints: any[] = _data.mountingPoints;
|
||||
|
||||
Status(`\tParsing ${mountingPoints.length} mounting points...`);
|
||||
|
||||
for (let m of mountingPoints) {
|
||||
for (let b of buildings) {
|
||||
if (b.id == m.objectId) {
|
||||
// Current mounting point is in current buidling?
|
||||
|
||||
b.utmRefPoint = Calc.LatLonToUtm(
|
||||
Calc.RadToDeg(b.refPoint.lat),
|
||||
Calc.RadToDeg(b.refPoint.lon)
|
||||
); // Converts refPoint of the building to UTM
|
||||
|
||||
m.fixedPosition = Calc.RotatePoint(
|
||||
{ x: m.position3d.x, y: m.position3d.y },
|
||||
Calc.DegToRad(b.rotationDegree)
|
||||
); // Rotates coords of the mounting point by rotationDegree of the building
|
||||
|
||||
m.absoluteUtm = b.utmRefPoint;
|
||||
m.absoluteUtm.Easting += m.fixedPosition.x;
|
||||
m.absoluteUtm.Northing += m.fixedPosition.y;
|
||||
m.absoluteLatLon = Calc.UtmToLatLon(m.absoluteUtm);
|
||||
// Add refPoint to the relative position and convert back to lat/lng
|
||||
|
||||
const arButtonHtml = isMobile
|
||||
? `<button
|
||||
onclick="window.Functions.DisplayInAr(${m.id})"
|
||||
id="arButton"
|
||||
>
|
||||
Show in AR
|
||||
</button>`
|
||||
: "<i>AR not supported on desktop.</i>";
|
||||
|
||||
for (let l of layers) {
|
||||
for (let i of l.Ids) {
|
||||
if (i == m.levelId) {
|
||||
// Current mouting point is on current level?
|
||||
|
||||
let marker = new MountingPoint(
|
||||
// Create a marker for the mounting point
|
||||
new L.LatLng(
|
||||
m.absoluteLatLon.lat,
|
||||
m.absoluteLatLon.lon
|
||||
),
|
||||
{
|
||||
icon: L.icon({
|
||||
iconUrl: "img/marker.png",
|
||||
iconSize: [30, 30]
|
||||
})
|
||||
}
|
||||
)
|
||||
.bindPopup(
|
||||
// Create a popup with info about the mounting point
|
||||
`<h2>
|
||||
${IsStringEmpty(m.tag) ? m.designation : m.tag}
|
||||
</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>${m.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Object ID</td>
|
||||
<td>${m.objectId}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Supported Asset Type IDs</td>
|
||||
<td>${m.supportedAssetTypeIds}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
${arButtonHtml}
|
||||
`
|
||||
)
|
||||
.addTo(map); // Add it to the map
|
||||
|
||||
marker.SupportedAssets = // Add info about supported asset types
|
||||
m.supportedAssetTypeIds;
|
||||
marker.ID = m.id; // Add mounting point id
|
||||
|
||||
l.LLayer.addLayer(marker); // Add marker to corresponding layer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.mountingPoints = mountingPoints;
|
||||
|
||||
// #endregion
|
||||
// #region ASSETS
|
||||
|
||||
// Get asset types
|
||||
let assets: any[] = _data.assetTypes;
|
||||
|
||||
Status(`\tParsing ${assets.length} assets...`);
|
||||
|
||||
// Add asset types to the dropdown menu
|
||||
$scope.assets = {
|
||||
availableAssets: assets,
|
||||
selectedAsset: assets[0]
|
||||
};
|
||||
|
||||
// #endregion
|
||||
// #region ANGULAR FUNCTIONS
|
||||
|
||||
// Called by either the layer or asset selection
|
||||
/** @function */ $scope.SelectionChanged = () => {
|
||||
let points: number = 0;
|
||||
for (let layer of layers) {
|
||||
if (layer.Selected) {
|
||||
map.addLayer(layer.LLayer); // Add selected layers
|
||||
|
||||
// Hacky type conversion to get the supported asset types from each mounting point on a layer
|
||||
// If it's stupid but it works it's not stupid
|
||||
for (let m of (layer.LLayer.getLayers() as unknown[]) as MountingPoint[]) {
|
||||
try {
|
||||
for (let a of m.SupportedAssets) {
|
||||
if (a == $scope.assets.selectedAsset.id) {
|
||||
m.addTo(map); // Add markers for selected asset type
|
||||
++points;
|
||||
} else {
|
||||
m.removeFrom(map); // Remove all other markers
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/** Sometimes theres an error here...
|
||||
* Doesn't seem to interfere with anything though and
|
||||
* I don't want to see it in the console
|
||||
*/
|
||||
}
|
||||
}
|
||||
} else map.removeLayer(layer.LLayer); // Remove unselected layers
|
||||
}
|
||||
console.log(
|
||||
`Selected ${points} mounting point${points == 1 ? "" : "s"}.`
|
||||
);
|
||||
};
|
||||
|
||||
// Filter for selected mounting point table and count
|
||||
/** @function */ $scope.MountingPointFilter = () => {
|
||||
return mp => {
|
||||
let correctAssetType: boolean = false;
|
||||
|
||||
for (let i of mp.supportedAssetTypeIds) {
|
||||
if (i == $scope.assets.selectedAsset.id) {
|
||||
correctAssetType = true;
|
||||
break;
|
||||
}
|
||||
} // Current mounting point supports selected asset type
|
||||
|
||||
let correctLayer: boolean = false;
|
||||
|
||||
for (let layer of layers) {
|
||||
for (let i of layer.Ids) {
|
||||
if (layer.Selected && i == mp.levelId) {
|
||||
correctLayer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // Current mounting point is on a selected layer
|
||||
|
||||
let foundBySearch: boolean = IsStringEmpty($scope.mpSearch)
|
||||
? true // Automatically true if search input empty
|
||||
: (IsStringEmpty(mp.tag) ? mp.designation : mp.tag).indexOf(
|
||||
$scope.mpSearch
|
||||
) >= 0 || mp.id.toString().indexOf($scope.mpSearch) >= 0;
|
||||
// true if tag, designation or id contain search input
|
||||
|
||||
return correctAssetType && correctLayer && foundBySearch;
|
||||
};
|
||||
};
|
||||
|
||||
// Function for the Show button
|
||||
/** @function */ $scope.ShowMp = mp => {
|
||||
for (let l of layers) {
|
||||
// Not this again...
|
||||
for (let m of (l.LLayer.getLayers() as unknown[]) as MountingPoint[]) {
|
||||
if (m.ID == mp.id) {
|
||||
// Find clicked mounting point
|
||||
m.openPopup(); // Open corresponding popup
|
||||
map.panTo(m.getLatLng()); // Center map on the popup
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
// Get final time and format it
|
||||
let time: string =
|
||||
t0 != null
|
||||
? `Finished loading data in ${performance.now() -
|
||||
t0}ms ########################################`
|
||||
: `Finished loading data #################################################`;
|
||||
console.log(time);
|
||||
|
||||
$scope.SelectionChanged(); // Make initial map state fit the initial menu state
|
||||
$scope.loading = false; // Done loading
|
||||
$scope.$apply();
|
||||
});
|
||||
});
|
||||
|
||||
// #region WIKITUDE FUNCTIONS
|
||||
|
||||
var currentPOI: any = undefined; // Holds current wikitude ar marker
|
||||
|
||||
// Shows an augmented reality marker vor the selected mounting point
|
||||
function _displayInAr(id: number): void {
|
||||
let $scope = GetScope(); // Retrieve angular scope
|
||||
let mountingPoint: any;
|
||||
|
||||
for (let mp of $scope.mountingPoints) {
|
||||
if (mp.id == id) {
|
||||
mountingPoint = mp;
|
||||
break;
|
||||
}
|
||||
} // Get correct mounting point
|
||||
|
||||
let name = IsStringEmpty(mountingPoint.tag)
|
||||
? mountingPoint.designation
|
||||
: mountingPoint.tag;
|
||||
|
||||
$scope.arMountingPoint = name;
|
||||
// Display mounting point tag/designation
|
||||
|
||||
currentPOI = new AR.GeoObject(
|
||||
[
|
||||
new AR.GeoLocation(
|
||||
mountingPoint.absoluteLatLon.lat,
|
||||
mountingPoint.absoluteLatLon.lon
|
||||
)
|
||||
],
|
||||
{
|
||||
drawables: {
|
||||
cam: [
|
||||
new AR.ImageDrawable(
|
||||
new AR.ImageResource("img/armarker.png"),
|
||||
2.5,
|
||||
{ zOrder: 0, opacity: 1 }
|
||||
),
|
||||
new AR.Label(name, 1, {
|
||||
zOrder: 1,
|
||||
style: { textColor: "#ffffff" }
|
||||
})
|
||||
],
|
||||
indicator: new AR.ImageDrawable(
|
||||
new AR.ImageResource("img/arindicator.png"),
|
||||
0.1,
|
||||
{
|
||||
enabled: true,
|
||||
verticalAnchor: AR.CONST.VERTICAL_ANCHOR.TOP
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
); // Create AR marker
|
||||
|
||||
$scope.arMode = true; // Hide map and menu
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
console.log("Switched to AR mode.");
|
||||
}
|
||||
|
||||
// Goes back to the map view
|
||||
function _backFromAr(): void {
|
||||
let $scope = GetScope(); // Retrieve angular scope
|
||||
|
||||
$scope.arMountingPoint = ""; // Clear name display
|
||||
$scope.arMode = false; // Show map and menu again
|
||||
|
||||
currentPOI.destroy();
|
||||
currentPOI = undefined;
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
console.log("Switched to map mode.");
|
||||
}
|
||||
|
||||
// #region TS -> JS
|
||||
/**
|
||||
* Hacky way to make typescript functions accessible from the html page
|
||||
* like in regular javascript
|
||||
*/
|
||||
|
||||
// Add "Functions"-object to window
|
||||
declare global {
|
||||
interface Window {
|
||||
Functions: any;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach functions
|
||||
window.Functions = window.Functions || {
|
||||
DisplayInAr: _displayInAr,
|
||||
BackFromAr: _backFromAr
|
||||
};
|
||||
// #endregion
|
||||
|
||||
// #endregion
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@types/angular": {
|
||||
"version": "1.6.54",
|
||||
"resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.54.tgz",
|
||||
"integrity": "sha512-xA1FuozWXeRQ7FClUbvk8ePL+dydBeDoCWRPFTHU5+8uvVtIIfLGiHA8CMkwsbddFCYnTDVbLxG85a/HBx7LtA=="
|
||||
},
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.5.tgz",
|
||||
"integrity": "sha512-rLlMXpd3rdlrp0+xsrda/hFfOpIxgqFcRpk005UKbHtcdFK+QXAjhBAPnvO58qF4O1LdDXrcaiJxMgstCIlcaw=="
|
||||
},
|
||||
"@types/leaflet": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.4.2.tgz",
|
||||
"integrity": "sha512-iRyUFh6m5OSbhekHxlZb5SkdfUVpaXhk+e8fgQviZL6iZ1chV3Q030/+M6jO2A6VM235A1hYuUmBMVsrLAvKEA==",
|
||||
"requires": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"angular": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/angular/-/angular-1.7.7.tgz",
|
||||
"integrity": "sha512-MH3JEGd8y/EkNCKJ8EV6Ch0j9X0rZTta/QVIDpBWaIdfh85/e5KO8+ZKgvWIb02MQuiS20pDFmMFlv4ZaLcLWg=="
|
||||
},
|
||||
"leaflet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.4.0.tgz",
|
||||
"integrity": "sha512-x9j9tGY1+PDLN9pcWTx9/y6C5nezoTMB8BLK5jTakx+H7bPlnbCHfi9Hjg+Qt36sgDz/cb9lrSpNQXmk45Tvhw=="
|
||||
},
|
||||
"utm-latlng": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utm-latlng/-/utm-latlng-1.0.5.tgz",
|
||||
"integrity": "sha1-rmBbTCLSapYVmoH2vLOSkyEj18I="
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/angular": "^1.6.54",
|
||||
"@types/leaflet": "^1.4.2",
|
||||
"angular": "^1.7.7",
|
||||
"leaflet": "^1.4.0",
|
||||
"utm-latlng": "^1.0.5"
|
||||
}
|
||||
}
|
||||
Executable
+624
@@ -0,0 +1,624 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg,
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer {
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile {
|
||||
will-change: opacity;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
-o-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
.leaflet-control-zoom-out {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in {
|
||||
font-size: 22px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path {
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 19px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 4px 0 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
|
||||
table {
|
||||
background-color: darkgray;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.bigBr {
|
||||
line-height: 200%;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#parentContainer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#connectionConatiner {
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
margin: auto;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
height: 100%;
|
||||
width: 66%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#guiContainer {
|
||||
height: 100%;
|
||||
width: 34%;
|
||||
float: right;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#arCtrl {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
top: 90vh;
|
||||
height: 10vh;
|
||||
width: 98%;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#mp {
|
||||
overflow: auto;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
|
||||
#map {
|
||||
height: 50%;
|
||||
width: 99%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#parentContainer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
float: unset;
|
||||
}
|
||||
|
||||
#guiContainer {
|
||||
position: fixed;
|
||||
overflow: auto;
|
||||
top: 55%;
|
||||
height: 45%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
float: unset;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": { "*": ["types/*"] },
|
||||
/* Basic Options */
|
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
"types": [
|
||||
"angular",
|
||||
"leaflet"
|
||||
] /* Type declaration files to be included in compilation. */,
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user