Leaflet map for displaying mounting points
Table of contents
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:
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:
<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-repeatfor 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 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. 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:
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:
- Transpile: Ctrl+Shift+B in VS Code, choose "build"
- Browserify:
browserify main.js -o scripts/main.js - 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-clickis basicallyonclick
- 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 Calcis a little collection of some math stuff:RadToDeg()andDegToRad()are really just one-liner functions to convert between degrees and radiansRotatePoint()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()andUtmToLatLon()are just wrappers to simplify the respective utm-latlng functions
class MountingPointis a leaflet marker which stores additional info about the mounting point it representsclass Layerstores the markers and is basically a representation of floors but floors with the same name are stored in the same layerIsStringEmpty()returnstruewhen the given string is either empty,nullorundefined- Any property of
$scopewithin the angular controller is visible to angular templates within index.html $scope.loadData()gets the data from the entered websocket and calls the eventwebSocketResponse$scope.$on("webSocketResponse", /* ... */): This handles thewebSocketResponse-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: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.
layeris an instance of myLayer-class.LLayeris 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 myMountingPoint-class because it returnsL.Markers.
More precisely, the values is still there but not accessible sinceL.Markerdoesn't have the properties.
But I can't directly cast fromL.MarkertoMountingPoint. Therefore I do a cheaty cast viaunknown.
It's confusing as heck but the only way I can easily get access to theMountingPoint-properties. -
The mean conditional statement at
foundBySearchwithin$scope.MountingPointFilter()is a short way of sayingtrueif 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.
If you are lucky I might remember how it works :p