Merge branch 'master' of git.uio.no:u/erikhf/frm
[u/erikhf/frm.git] / src / components / map / map.ts
1 import {Component, EventEmitter,CORE_DIRECTIVES} from 'angular2/angular2';
2 import {Headers, Http} from 'angular2/http';
3
4 @Component({
5     selector: 'mou-map',
6     directives: [CORE_DIRECTIVES],
7     events: ['newactive', 'neworg'],
8     templateUrl: './components/map/map.html'
9 })
10
11
12 export class Map {
13
14     map:Object;
15     http:Http;
16     LEVEL:number;
17     allLevels:Object;
18     runned:boolean;
19     uprunned:boolean;
20     parent:Object;
21     activeId:string;
22     currentPos:Object;
23     currentMarker:Object;
24     isSearched:boolean;
25     COLORS:Object;
26     colornum:number;
27     isNew:boolean;
28     //popupON:boolean;
29     //popup:Object;
30
31
32     /**
33      * initializes all the global variabels
34      * @param http - for http requests
35      */
36     constructor(http:Http) {
37
38         this.activeId = null;
39         this.newactive = new EventEmitter();
40         this.neworg = new EventEmitter();
41         this.map = new google.maps.Map(document.getElementById("map"), {
42             center: {lat: 0, lng: 0},
43             zoom: 12,
44             mapTypeControlOptions: {
45                 position: google.maps.ControlPosition.BOTTOM_CENTER
46             },
47             zoomControlOptions: {
48                 position: google.maps.ControlPosition.LEFT_BOTTOM
49             },
50             streetViewControl: false
51         });
52         this.init();
53         this.http = http;
54         this.LEVEL = 2;
55         this.runned = false;
56         this.getLevels(this);
57         this.parent = null;
58         this.currentPos = null;
59         this.uprunned = false;
60         this.currentMarker = null;
61         this.isSearched = false;
62         this.isNew = false;
63         this.colornum = 0;
64         this.COLORS = ['#ede1bb', '#1d407e', '#ff512e', '#662d47', '#3b3a35', '#419175', '#983e41', '#f3002d', '#b0a875', '#00bfb5', '#926851', '#47a0a4', '#333f50', '#6f007b'];
65         //this.popupON = false;
66         //this.popup = null;
67     }
68
69     /**
70      * Sets the global variabel
71      * @param id - id of the active marker
72      */
73     setActiveId(id) {
74         this.activeId = id;
75     }
76
77     /**
78      * returns the global map
79      * @returns {Object}
80      */
81     getMap() {
82         return this.map;
83     }
84
85     /**
86      * returns global http
87      * @returns {Http}
88      */
89     getHttp() {
90         return this.http;
91     }
92
93     /**
94      * Sets the avctive markers position
95      * @param latlng - position of the active marker
96      */
97     setcurrentPos(latlng) {
98         this.currentPos = latlng;
99     }
100
101     /**
102      * returns the active markers position
103      * @returns {Object}
104      */
105     getcurrentPos() {
106         return this.currentPos;
107     }
108
109     /**
110      * sets the parent of the avtive marker
111      * @param id - of the parent
112      */
113     setParent(id) {
114         this.parent = id;
115     }
116
117
118     /**
119      * returns the actice markers parent
120      * @returns {Object}
121      */
122     getParent() {
123         return this.parent;
124     }
125
126     /**
127      * sets a bool value for if the addListner for drilling down has runned (little hack)
128      * @param value - for the runned variabel
129      */
130     setRunned(value) {
131         this.runned = value;
132     }
133
134     /**
135      * sets a bool value for if the addListner for drilling up has runned (little hack)
136      * @param value - for the upRunned variabel
137      */
138     setupRunned(value) {
139         this.uprunned = value;
140     }
141
142     /**
143      * sets the current level in the org.unit hierarchy
144      * @param value - for the level variabel
145      */
146     setLevel(value) {
147         this.LEVEL = value;
148     }
149
150     /**
151      * add level when drilling down (little hack for synconisity)
152      */
153     addLevel() {
154         this.LEVEL++;
155     }
156
157     /**
158      * goes up level when drilling up (little hack for synconisity)
159      */
160     upLevel() {
161         this.LEVEL--;
162     }
163
164     /**
165      * initiates the map with position and zoom
166      */
167     init() {
168
169         let map = this.map;
170         let pos = {lat: 9.1, lng: -11.6};
171         map.setCenter(pos, 0);
172         map.setZoom(7);
173
174     }
175
176     /**
177      * prints out error messages in the console
178      * @param error - the error massage
179      */
180     logError(error) {
181         console.error(error);
182
183     }
184
185     /**
186      * gets data from DHIS API
187      * @param query - for what kind of data to retrieve
188      * @param instance - this instance to use
189      * @param isParent - little hack to see if you want to levels up (the parent of a parent)
190      */
191     getData(query, instance, isParent) {
192         instance.http.get(dhisAPI + '/api/organisationUnits' + query)
193             .map(res => res.json())
194             .subscribe(
195                 res => instance.parseResult(res, instance, isParent),
196                 error => instance.logError(error)
197             );
198     }
199
200     /**
201      * Gets the number of levels in the org.unit hierarchy from DHIS
202      */
203     getLevels() {
204         this.http.get(dhisAPI + '/api/organisationUnitLevels')
205             .map(res => res.json())
206             .subscribe(
207                 res => this.saveLevelTotalandGetdata(res, this),
208                 err => this.logError(err)
209             );
210     }
211
212     /**
213      * Saves the data from getLevels() in a global variabel and gets all the data from the second level.
214      * @param res - result from getLevels()
215      * @param instance - witch scope we are in
216      */
217     saveLevelTotalandGetdata(res, instance) {
218         instance.allLevels = res.pager.total;
219         instance.getData('?paging=false&level=2', instance, false);
220     }
221
222     /**
223      * parses all the data from getData() and calles methods based on the incomming data.
224      * @param res - result from getData()
225      * @param instance - witch scope we are in
226      * @param isParent - if it is a parent we have asked for
227      */
228     parseResult(res, instance, isParent) {
229         if (isParent) {
230             instance.setParent(res.parent.id);
231             instance.getData('/' + res.parent.id + '/children', instance, false);
232         }
233         else {
234             if (res.organisationUnits) {
235                 for (let item in res.organisationUnits) {
236                     this.getData('/' + res.organisationUnits[item].id, this);
237
238                 }
239                 instance.setupRunned(false);
240                 instance.setRunned(false);
241             } else if (!res.displayName && res.children) {
242                 for (let item in res.children) {
243                     if (res.children[item].level == instance.LEVEL) {
244                         this.getData('/' + res.children[item].id, this);
245                     }
246                 }
247                 instance.setRunned(false);
248                 instance.setupRunned(false);
249             }
250             else {
251                 this.drawPolygon(res, instance);
252             }
253         }
254     }
255
256     /**
257      * creates and draws up the geojson polygons and adds listeners to them.
258      * @param item - an org.unit object
259      * @param instance - witch scope we are in
260      */
261     drawPolygon(item, instance) {
262         let feature;
263         let incoming:string;
264         incoming = item.featureType.toLowerCase();
265         switch (incoming) {
266             case "point":
267                 feature = 'Point';
268                 break;
269             case "multi_polygon":
270                 feature = 'MultiPolygon';
271                 break;
272             case "polygon":
273                 feature = 'MultiPolygon';
274                 break;
275             default:
276         }
277
278         if (feature !== undefined) {
279             let unit = {
280                 "type": "Feature",
281                 "geometry": {
282                     "type": feature,
283                     "coordinates": JSON.parse(item.coordinates)
284                 },
285                 "properties": {
286                     "title": item.name,
287                     "name": item.name,
288                     "id": item.id,
289                     "color": instance.COLORS[instance.colornum],
290                     "icon": null
291                 }
292             };
293             if (instance.COLORS.length == instance.colornum) {
294                 instance.colornum = 0;
295             } else {
296                 instance.colornum++;
297             }
298
299             if (unit.geometry.type == 'Point') {
300                 unit.properties.icon = {
301                     path: google.maps.SymbolPath.CIRCLE,
302                     strokeColor: 'black',
303                     scale: 4
304                 };
305                 instance.map.setCenter({lat: unit.geometry.coordinates[1], lng: unit.geometry.coordinates[0]});
306             }
307
308             this.map.data.addGeoJson(unit);
309             this.map.data.setStyle(function (feature) {
310                 let color = 'gray';
311                 let icon;
312                 if (feature.getProperty('icon') !== null) {
313                     icon = feature.getProperty('icon');
314                 }
315                 color = feature.getProperty('color');
316                 return /** @type {google.maps.Data.StyleOptions} */({
317                     fillColor: color,
318                     fillOpacity: 0.91,
319                     strokeColor: 'white',
320                     strokeWeight: 2,
321                     icon: icon
322                 });
323             });
324
325             if (this.isSearched) {
326                 this.seeDetails();
327             }
328             this.map.data.addListener('click', function (event) {
329                 $('#myModal').modal('show');
330                 instance.setActiveId(event.feature.O.id);
331                 instance.setcurrentPos(event.latLng);
332
333                 if (instance.uprunned == false && instance.LEVEL == 2) {
334                     document.getElementById("topLevel").style.display = "block";
335                     document.getElementById("middleLevel").style.display = "none";
336                     document.getElementById("bottomLevel").style.display = "none";
337                 }
338                 else if (instance.runned == false && instance.LEVEL < instance.allLevels) {
339                     document.getElementById("topLevel").style.display = "none";
340                     document.getElementById("middleLevel").style.display = "block";
341                     document.getElementById("bottomLevel").style.display = "none";
342                 } else if (instance.runned == false && instance.LEVEL <= instance.allLevels) {
343                     document.getElementById("topLevel").style.display = "none";
344                     document.getElementById("middleLevel").style.display = "none";
345                     document.getElementById("bottomLevel").style.display = "block";
346
347                     instance.setcurrentPos(event.latLng);
348                 }
349             });
350
351             // a method for giving a popup with the org.units name when the mouse is over the plygon
352             //commented out because somethimes it made it harder to click on the polygon to get options
353             // to see more or drillup/down
354             /*   this.map.data.addListener('mouseover', function (e) {


355              if(!instance.popupON) {
356              instance.popupON = true;
357
358              instance.popup = new google.maps.InfoWindow({
359              content: e.feature.getProperty('name'),
360              position: e.latLng
361              });
362              instance.popup.open(instance.map);
363
364              }
365              });

366              this.map.data.addListener('mouseout', function (event) {


367              instance.popupON = false;
368              instance.popup.open(null);

369              });
370              */
371         }
372     }
373
374     /**
375      * utpdates the map after an org.unit has been updated or a new one has been added
376      * @param event - id of the new or updated org.unit
377      */
378     updateAfterAddorEdit(event) {
379         if (this.currentMarker) {
380             this.currentMarker.setMap(null);
381         }
382
383         let map = this.getMap();
384         let http = this.getHttp();
385         this.isNew = false;
386
387
388         map.data.forEach(function (feature) {
389             map.data.remove(feature);
390         });
391         http.get(dhisAPI + '/api/organisationUnits/' + event)
392             .map(res => res.json())
393             .subscribe(
394                 res => this.updateMap(res, this)
395             );
396     }
397
398     /**
399      * shows the right level with the right org.units after one has been added or updated.
400      * @param res
401      * @param instance
402      */
403     updateMap(res, instance) {
404         this.isSearched = false;
405         this.setLevel(res.level);
406         this.setActiveId(res.id);
407         this.setParent(res.parent.id);
408         this.setcurrentPos(new google.maps.LatLng(JSON.parse(res.coordinates)[1],JSON.parse(res.coordinates)[0]));
409
410         instance.getData('/' + res.parent.id + '/children', instance);
411         if (res.coordinates == null || instance.LEVEL == instance.allLevels) {
412             instance.http.get(dhisAPI + '/api/organisationUnits/' + res.parent.id)
413                 .map(res => res.json())
414                 .subscribe(
415                     res => instance.drawPolygon(res, instance)
416                 );
417         }
418
419     }
420
421     /**
422      * removes the polygon on current level and calles getData on one level down in the org.unit hierarchy
423      */
424     drillDown() {
425         this.isSearched = false;
426         this.closeModal();
427         let map = this.getMap();
428         let id = this.activeId;
429         let level = this.LEVEL;
430         let lev = (this.allLevels) - 1;
431         this.setRunned(true);
432         this.setParent(id);
433
434         map.data.forEach(function (feature) {
435             if (!(feature.O.id == id && level == lev)) {
436                 map.data.remove(feature);
437
438             }
439         });
440
441         this.addLevel();
442         this.getData('/' + id + '/children', this);
443
444     }
445
446     /**
447      *removes the plogons on the current level and calles the get data with tha parents id and set parent true. this to say that we want this parent's parent
448      */
449     drillUp() {
450         this.isSearched = false;
451         this.setupRunned(true);
452         this.upLevel();
453         let instance = this;
454         this.closeModal();
455         this.map.data.forEach(function (feature) {
456             instance.map.data.remove(feature);
457
458         });
459         if (this.currentMarker !== null) {
460             this.currentMarker.setMap(null);
461         }
462         let parent = instance.getParent();
463         instance.getData('/' + parent, instance, true);
464
465         this.closeModal();
466     }
467
468     /**
469      * focuses map and colors to the clicked marker/polygon and fires an event to sidebar with the id of the marker
470      */
471     seeDetails() {
472         let map = this.getMap();
473         let id = this.activeId;
474         this.closeModal();
475         map.data.forEach(function (feature) {
476             if (feature.getProperty('id') == id) {
477                 feature.setProperty('color', 'red');
478                 if (feature.getProperty('icon') !== null) {
479                     feature.O.icon.strokeColor = 'red';
480                 }
481
482             }
483             else {
484                 feature.setProperty('color', 'gray');
485                 if (feature.getProperty('icon') !== null) {
486                     feature.O.icon.strokeColor = 'black';
487                 }
488             }
489         });
490         this.newactive.next(this.activeId);
491     }
492
493     /**
494      * gets the position of the clicked position on the map, saves the parent and sends it in an event.
495      */
496     addUnit() {
497         this.closeModal();
498         this.isNew = true;
499         let pos = this.getcurrentPos();
500         let lat = pos.lat();
501         let lng = pos.lng();
502         let parent = this.getParent();
503
504         let location = {lat: lat, lng: lng};
505         let event = {location, parent};
506         this.neworg.next(event);
507         this.closeModal();
508         this.setRunned(false);
509     }
510
511     /**
512      * triggered from an event in search and gets the search object from the DHIS API
513      * then calles mapupdate()
514      * @param event - event from an emitter
515      */
516     update(event) {
517         this.newactive.next(event);
518         let map = this.getMap();
519         let http = this.getHttp();
520
521         map.data.forEach(function (feature) {
522             map.data.remove(feature);
523         });
524         http.get(dhisAPI + '/api/organisationUnits/' + event)
525             .map(res => res.json())
526             .subscribe(
527                 res => this.mapUpdate(res, this)
528             );
529
530     }
531
532
533     /**
534      * updates varabels activeId, level and parent to matche the incomming object and gets all the children on the same level.
535      * Then it calles drawPolygon()
536      * @param res - org.unit object
537      * @param instance
538      */
539     mapUpdate(res, instance) {
540         this.setLevel(res.level);
541         this.setActiveId(res.id);
542         this.isSearched = true;
543         this.setParent(res.parent.id);
544
545         instance.getData('/' + res.parent.id + '/children', instance);
546         if (res.coordinates == null || instance.LEVEL == instance.allLevels) {
547             instance.http.get(dhisAPI + '/api/organisationUnits/' + res.parent.id)
548                 .map(res => res.json())
549                 .subscribe(
550                     res => instance.drawPolygon(res, instance)
551                 );
552         }
553
554     }
555
556     /**
557      * adds a temperary marker so the user can see an update of the latitude and longitude of a marker
558      * @param pos - position for the temp marker
559      */
560     tempMarker(pos) {
561         if (this.currentMarker) {
562             this.currentMarker.setMap(null);
563         }
564         if (pos != null) {
565             let current = {lat: null, lng: null};
566             current.lat = Math.round(this.getcurrentPos().lat() * 10000) / 10000;
567             current.lng = Math.round(this.getcurrentPos().lng() * 10000) / 10000;
568
569             let position = {lat: null, lng: null};
570             position.lat = Math.round(pos.lat * 10000) / 10000;
571             position.lng = Math.round(pos.lng * 10000) / 10000;
572
573
574             if ((current.lat != position.lat) || (current.lng != position.lng) || this.isNew) {
575
576                 let map = this.map;
577                 if (this.currentMarker)
578                     this.currentMarker.setMap(null);
579
580                 this.currentMarker = new google.maps.Marker({
581                     position: pos,
582                     map: map,
583                     title: 'neworg',
584                     icon: {
585                         path: google.maps.SymbolPath.CIRCLE,
586                         strokeColor: '#871F78',
587                         scale: 4
588                     }
589                 });
590                 this.currentMarker.setMap(map);
591                 map.panTo(this.currentMarker.getPosition());
592             }
593         }
594     }
595
596
597     /**
598      * closes the modal box over the map.
599      */
600     closeModal() {
601         $("#myModal").modal("hide");
602         this.setRunned(false);
603     }
604
605 }
606
607
608
609
610