map.component.ts 9.78 KB
Newer Older
Hugo SUBTIL's avatar
Hugo SUBTIL committed
1
2
import { Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { latLng, MapOptions, geoJSON, tileLayer, Map, latLngBounds, layerGroup } from 'leaflet';
3
import { Structure } from '../../models/structure.model';
4
import { GeojsonService } from '../../services/geojson.service';
5
import { MapService } from '../services/map.service';
Jérémie BRISON's avatar
Jérémie BRISON committed
6
import * as _ from 'lodash';
7
8
import { GeoJsonProperties } from '../models/geoJsonProperties.model';
import { MarkerType } from './markerType.enum';
9
10
import metropole from '../../../assets/geojson/metropole.json';
import brignais from '../../../assets/geojson/brignais.json';
Hugo SUBTIL's avatar
Hugo SUBTIL committed
11
import L from 'leaflet';
Hugo SUBTIL's avatar
Hugo SUBTIL committed
12
import 'leaflet.locatecontrol';
Hugo SUBTIL's avatar
Hugo SUBTIL committed
13
14
15
16
17
18

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.scss'],
})
19
20
export class MapComponent implements OnChanges {
  @Input() public structures: Structure[] = [];
21
  @Input() public structuresToPrint: Structure[] = [];
22
23
  @Input() public toogleToolTipId: string;
  @Input() public selectedMarkerId: string;
Jérémie BRISON's avatar
Jérémie BRISON committed
24
  @Input() public isMapPhone: boolean;
Hugo SUBTIL's avatar
Hugo SUBTIL committed
25
  @Input() public locate = false;
Hugo SUBTIL's avatar
Hugo SUBTIL committed
26
  @Input() public searchedValue: string;
Jérémie BRISON's avatar
Jérémie BRISON committed
27
  @Output() selectedStructure: EventEmitter<Structure> = new EventEmitter<Structure>();
28
  @Output() locatationTrigger: EventEmitter<boolean> = new EventEmitter<boolean>();
Hugo SUBTIL's avatar
Hugo SUBTIL committed
29
  private lc; // Locate control
Jérémie BRISON's avatar
Jérémie BRISON committed
30
31
  private currentStructure: Structure;

Hugo SUBTIL's avatar
Hugo SUBTIL committed
32
33
  public map: Map;
  public mapOptions: MapOptions;
34
  // Init locate options
35
36
  public locateOptions = {
    flyTo: false,
37
    keepCurrentZoomLevel: false,
38
39
    icon: 'fa-map-marker',
    clickBehavior: { inView: 'stop', outOfView: 'setView', inViewNotFollowing: 'setView' },
40
    circlePadding: [5, 5],
41
  };
Hugo SUBTIL's avatar
Hugo SUBTIL committed
42

Jérémie BRISON's avatar
Jérémie BRISON committed
43
44
45
46
47
48
49
50
  // Add listener on the popup button to show details of structure
  @HostListener('document:click', ['$event'])
  public clickout(event): void {
    if (event.target.classList.contains('btnShowDetails')) {
      this.selectedStructure.emit(this.currentStructure);
    }
  }

51
  constructor(private mapService: MapService, private geoJsonService: GeojsonService) {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
52
53
54
    this.initializeMapOptions();
  }

55
  ngOnChanges(changes: SimpleChanges): void {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
56
    if (changes.searchedValue && !changes.searchedValue.firstChange) {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
57
58
59
60
61
      if (changes.searchedValue.currentValue) {
        this.processTownCoordinate(changes.searchedValue.currentValue);
      } else {
        this.map.setView(this.mapOptions.center, this.mapOptions.zoom);
      }
Hugo SUBTIL's avatar
Hugo SUBTIL committed
62
    }
Jérémie BRISON's avatar
Jérémie BRISON committed
63
64
65
66
67
68
69
    if (changes.isMapPhone) {
      if (this.isMapPhone) {
        setTimeout(() => {
          this.map.invalidateSize();
        }, 0);
      }
    }
Hugo SUBTIL's avatar
Hugo SUBTIL committed
70
71
72
73
74
75
76
77
    // Handle map locate from search bar
    if (changes.locate && !changes.locate.isFirstChange()) {
      if (changes.locate.currentValue) {
        this.lc.start();
      } else {
        this.lc.stop();
      }
    }
78
    if (changes.structures) {
Jérémie BRISON's avatar
Jérémie BRISON committed
79
      this.handleStructurePosition(changes.structures.previousValue);
80
    }
81
    // Handle map marker tooltip
82
83
    if (changes.toogleToolTipId && changes.toogleToolTipId.currentValue !== changes.toogleToolTipId.previousValue) {
      if (changes.toogleToolTipId.previousValue !== undefined) {
84
85
86
87
88
        if (this.isToPrint(changes.toogleToolTipId.previousValue)) {
          this.mapService.setAddedToListMarker(changes.toogleToolTipId.previousValue);
        } else {
          this.mapService.setUnactiveMarker(changes.toogleToolTipId.previousValue);
        }
Jérémie BRISON's avatar
Fix/map    
Jérémie BRISON committed
89
90
91
      }
      if (changes.toogleToolTipId.currentValue !== undefined) {
        this.mapService.setActiveMarker(changes.toogleToolTipId.currentValue);
92
      }
93
94
    }
    // Handle map marker selection
Jérémie BRISON's avatar
Jérémie BRISON committed
95
    if (changes.selectedMarkerId && this.map) {
96
      this.map.closePopup();
97
98
      if (changes.selectedMarkerId.currentValue === undefined) {
        this.mapService.setDefaultMarker(changes.selectedMarkerId.previousValue);
99
        this.map.setView(this.mapOptions.center, this.mapOptions.zoom);
100
101
102
103
      } else {
        this.mapService.setSelectedMarker(changes.selectedMarkerId.currentValue);
        this.centerLeafletMapOnMarker(changes.selectedMarkerId.currentValue);
      }
104
    }
105
106
107
108
109
110
111
112
113

    if (changes.structuresToPrint) {
      if (changes.structuresToPrint.currentValue < changes.structuresToPrint.previousValue) {
        this.mapService.setUnactiveMarker(this.toogleToolTipId);
      }
      this.structuresToPrint.forEach((structure: Structure) => {
        this.mapService.setAddedToListMarker(structure._id);
      });
    }
114
115
  }

Hugo SUBTIL's avatar
Hugo SUBTIL committed
116
117
118
  public processTownCoordinate(queryString: string): void {
    this.geoJsonService.getTownshipCoord(queryString).subscribe(
      (townData) => {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
119
120
121
122
        if (townData.length > 0) {
          const bounds = new L.LatLngBounds(townData.map((dataArray) => dataArray.reverse()));
          this.map.fitBounds(bounds);
        }
Hugo SUBTIL's avatar
Hugo SUBTIL committed
123
124
125
126
127
128
129
      },
      (err) => {
        this.map.setView(this.mapOptions.center, this.mapOptions.zoom);
      }
    );
  }

130
131
132
  /**
   * Get structures positions and add marker corresponding to those positons on the map
   */
Jérémie BRISON's avatar
Jérémie BRISON committed
133
134
135
136
137
138
139
  private handleStructurePosition(previousStructuresValue: Structure[]): void {
    // If there is more structure than before, append them
    if (
      previousStructuresValue &&
      previousStructuresValue.length > 0 &&
      previousStructuresValue.length < this.structures.length
    ) {
140
      this.getStructuresPositions(_.differenceWith(this.structures, previousStructuresValue, _.isEqual));
Jérémie BRISON's avatar
Jérémie BRISON committed
141
142
143
    } else if (this.structures) {
      this.map = this.mapService.cleanMap(this.map);
      this.getStructuresPositions(this.structures);
144
145
146
147
      this.structuresToPrint.forEach((structure: Structure) => {
        this.mapService.setAddedToListMarker(structure._id);
      });

Jérémie BRISON's avatar
Jérémie BRISON committed
148
149
150
    }
  }

151
152
153
154
  private isToPrint(id: String): boolean {
    return this.structuresToPrint.findIndex((elem) => elem._id == id) > -1 ? true : false;
  }

Hugo SUBTIL's avatar
Hugo SUBTIL committed
155
156
  private getStructuresPositions(structureList: Structure[]): void {
    structureList.forEach((structure: Structure) => {
157
158
159
      this.mapService
        .createMarker(
          structure.getLat(),
Hugo SUBTIL's avatar
Hugo SUBTIL committed
160
          structure.getLon(),
161
          MarkerType.structure,
162
          structure._id,
163
164
165
166
167
168
169
          this.buildToolTip(structure)
        )
        .addTo(this.map)
        // store structure before user click on button
        .on('popupopen', () => {
          this.currentStructure = structure;
        });
170
    });
Hugo SUBTIL's avatar
Hugo SUBTIL committed
171
172
173
174
175
    // Reset location if active to prevent graphical issue
    if (this.locate) {
      this.lc.stop();
      this.lc.start();
    }
176
177
178
179
180
181
182
  }

  /**
   * Create tooltip for display
   * @param structure Structure
   */
  private buildToolTip(structure: Structure): string {
Hugo SUBTIL's avatar
Hugo SUBTIL committed
183
184
185
186
187
188
189
190
    let cssAvailabilityClass = structure.isOpen ? 'available' : null;
    if (cssAvailabilityClass === null) {
      if (structure.openedOn.day) {
        cssAvailabilityClass = 'unavailable';
      } else {
        cssAvailabilityClass = 'unknown';
      }
    }
191
192
    return (
      '<h1>' +
193
      structure.structureName +
194
195
      '</h1>' +
      '<p>' +
Hugo SUBTIL's avatar
Hugo SUBTIL committed
196
      structure.getLabelTypeStructure() +
197
198
199
200
201
      '</p><div>' +
      '<span class="ico-dot-' +
      cssAvailabilityClass +
      '"></span><span>' +
      structure.openDisplay() +
Jérémie BRISON's avatar
Jérémie BRISON committed
202
      '</span></div><div class="pop-up"><button type="button" class="btnShowDetails">Voir</button></div>'
203
204
205
    );
  }

206
207
208
209
  private buildMdmPopUp(mdmProperties: GeoJsonProperties): string {
    return `<h1>${mdmProperties.nom}</h1><p>${mdmProperties.adresse}</p>`;
  }

210
211
  /**
   * Add marker when map is ready to be showned
212
   * @param map map
213
   */
Hugo SUBTIL's avatar
Hugo SUBTIL committed
214
215
  public onMapReady(map: Map): void {
    this.map = map;
Hugo SUBTIL's avatar
Hugo SUBTIL committed
216
217
218
219
220
    // Handle location
    this.lc = L.control.locate(this.locateOptions).addTo(this.map);
    this.map.on('locationfound', () => {
      this.locatationTrigger.emit(true);
    });
221
222
  }

223
224
225
226
227
  /**
   * Init map options :
   * - Metropole bounds based on a WMS service hosted by data.grandlyon.com
   * - Map Layer based on open street maps
   */
Hugo SUBTIL's avatar
Hugo SUBTIL committed
228
  private initializeMapOptions(): void {
Jérémie BRISON's avatar
Jérémie BRISON committed
229
230
    // Init mdm
    this.initMDMLayer();
231
    // Init WMS service with param from data.grandlyon.com
232
    layerGroup();
233
234
235
    const carteLayer = tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
      attribution: '&copy; <a href="https://carto.com/attributions">CARTO</a>',
      maxZoom: 19,
236
    });
237
238
    // Center is set on townhall
    // Zoom is blocked on 11 to prevent people to zoom out from metropole
Hugo SUBTIL's avatar
Hugo SUBTIL committed
239
240
    this.mapOptions = {
      center: latLng(45.764043, 4.835659),
241
      maxZoom: 19,
Hugo SUBTIL's avatar
Hugo SUBTIL committed
242
      zoom: 12,
243
      minZoom: 10,
244
      layers: [carteLayer],
Hugo SUBTIL's avatar
Hugo SUBTIL committed
245
246
247
    };
  }

Jérémie BRISON's avatar
Jérémie BRISON committed
248
249
250
  private initMDMLayer(): void {
    this.geoJsonService.getMDMGeoJson().subscribe((res) => {
      res.forEach((mdm) => {
251
252
253
        this.mapService
          .createMarker(
            mdm.geometry.getLat(),
Hugo SUBTIL's avatar
Hugo SUBTIL committed
254
            mdm.geometry.getLon(),
255
256
257
258
259
            MarkerType.mdm,
            null,
            this.buildMdmPopUp(mdm.properties)
          )
          .addTo(this.map);
Jérémie BRISON's avatar
Jérémie BRISON committed
260
      });
261
262
      this.initBrignaisLayer();
      this.initMetropoleLayer();
Jérémie BRISON's avatar
Jérémie BRISON committed
263
264
265
    });
  }

266
  private centerLeafletMapOnMarker(markerId: string): void {
267
268
269
    const marker = this.mapService.getMarker(markerId);
    const latLngs = [marker.getLatLng()];
    const markerBounds = latLngBounds(latLngs);
270
271
    // paddingTopLeft is used for centering marker because of structure details pane
    this.map.fitBounds(markerBounds, { paddingTopLeft: [300, 0] });
272
  }
273
274
275
276
277
278
279
280

  private initBrignaisLayer(): void {
    this.map.addLayer(
      geoJSON(
        {
          type: brignais.features[0].geometry.type,
          coordinates: brignais.features[0].geometry.coordinates,
        } as any,
281
        { style: () => ({ color: '#a00000', fillOpacity: 0, weight: 1 }) }
282
283
284
285
286
287
288
289
290
291
292
      )
    );
  }

  private initMetropoleLayer(): void {
    this.map.addLayer(
      geoJSON(
        {
          type: metropole.features[0].geometry.type,
          coordinates: metropole.features[0].geometry.coordinates,
        } as any,
293
        { style: () => ({ color: '#a00000', fillOpacity: 0, weight: 1 }) }
294
295
296
      )
    );
  }
Hugo SUBTIL's avatar
Hugo SUBTIL committed
297
}