# 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 ` can be used to transpile "file.ts", `tsc --watch ` 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
    • {{x}}
  • ``` 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