File

src/app/syntheseModule/synthese-results/synthese-carte/synthese-carte.component.ts

Metadata

selector pnx-synthese-carte
styleUrls synthese-carte.component.scss
templateUrl synthese-carte.component.html

Inputs

inputSyntheseData

Type: any

Outputs

onAreasToggle $event type: EventEmitter

Constructor

constructor(mapListService: any, _ms: any, formService: any, _commonService: any, translateService: any, config: any)

Methods

Private initializeFormWithMapParams
initializeFormWithMapParams()
Returns: void
Private onLanguageChange
onLanguageChange()
Returns: void
Private defineI18nMessages
defineI18nMessages()
Returns: void
addAreasButton
addAreasButton()
Returns: void
Private addAreasLegend
addAreasLegend()
Returns: void
Private removeAreasLegend
removeAreasLegend()
Returns: void
layerDictCache
layerDictCache(idSyntheseList: any, layer: any)
Returns: void
layerEvent
layerEvent(feature: any, layer: any, idSyntheseIds: any)
Returns: void
onEachFeature
onEachFeature(feature: any, layer: any)
Returns: void
clusterCountOverrideFn
clusterCountOverrideFn(cluster: any)
Returns: void
Private setAreasStyle
setAreasStyle(layer: any, obsNbr: any)
Returns: void
Private getColor
getColor(obsNbr: any)
Returns: void
toggleStyleFromMap
toggleStyleFromMap(feature: any, layer: any)
Returns: void
Private toggleStyleFromList
toggleStyleFromList(currentSelectedLayers: any)
Returns: void
Private bindAreasPopup
bindAreasPopup(layer: any, ids: any)
Returns: void
bindGeojsonForm
bindGeojsonForm(geojson: any)
Returns: void
onFileLayerLoaded
onFileLayerLoaded(geojson: any)
Returns: void
deleteControlValue
deleteControlValue()
Returns: void

Properties

Private areasEnable
areasEnable: any
Private areasLabelSwitchBtn
areasLabelSwitchBtn: any
Private areasLegend
areasLegend: any
Public cluserOrSimpleFeatureGroup
cluserOrSimpleFeatureGroup: any
config
config: any
Public currentLeafletDrawCoord
currentLeafletDrawCoord: any
Private defaultIcon
defaultIcon: any
Private destroy$
destroy$: any
Private enableFitBounds
enableFitBounds: boolean
Default value: true
Public firstFileLayerMessage
firstFileLayerMessage: boolean
Default value: true
formService
formService: any
Public layersDict
layersDict: object
Public leafletDrawOptions
leafletDrawOptions: any
Default value: leafletDrawOption
mapListService
mapListService: any
Private originAreasStyle
originAreasStyle: { color: string; weight: number; fillOpacity: number; }
Private originDefaultStyle
originDefaultStyle: { color: string; weight: number; fill: boolean; }
Private selectedAreasStyle
selectedAreasStyle: { color: string; weight: number; }
Private selectedDefaultStyle
selectedDefaultStyle: { color: string; }
Private selectedIcon
selectedIcon: any
Public selectedLayers
selectedLayers: any[]
Public SYNTHESE_CONFIG
SYNTHESE_CONFIG: any
import {
  Component,
  OnInit,
  Input,
  AfterViewInit,
  EventEmitter,
  OnChanges,
  Output,
  OnDestroy,
} from '@angular/core';
import { GeoJSON } from 'leaflet';
import { MapListService } from '@geonature_common/map-list/map-list.service';
import { MapService } from '@geonature_common/map/map.service';
import { leafletDrawOption } from '@geonature_common/map/leaflet-draw.options';
import { SyntheseFormService } from '@geonature_common/form/synthese-form/synthese-form.service';
import { CommonService } from '@geonature_common/service/common.service';
import * as L from 'leaflet';
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ConfigService } from '@geonature/services/config.service';

export type EventToggle = 'grid' | 'point';

@Component({
  selector: 'pnx-synthese-carte',
  templateUrl: 'synthese-carte.component.html',
  styleUrls: ['synthese-carte.component.scss'],
  providers: [],
})
export class SyntheseCarteComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
  public leafletDrawOptions = leafletDrawOption;
  public currentLeafletDrawCoord: any;
  public firstFileLayerMessage = true;
  public SYNTHESE_CONFIG = null;
  public cluserOrSimpleFeatureGroup = null;

  private destroy$: Subject<boolean> = new Subject<boolean>();

  private areasEnable;
  private areasLegend;
  private enableFitBounds = true;
  private areasLabelSwitchBtn;
  public selectedLayers: Array<L.Layer> = [];
  public layersDict: object = {};

  private originDefaultStyle = {
    color: '#3388FF',
    weight: 3,
    fill: false,
  };
  private selectedDefaultStyle = {
    color: '#FF0000',
  };
  private originAreasStyle = {
    color: '#FFFFFF',
    weight: 0.4,
    fillOpacity: 0.8,
  };
  private selectedAreasStyle = {
    color: '#FF0000',
    weight: 3,
  };

  private defaultIcon = new L.Icon({
    iconUrl: 'assets/images/default_marker.png',
    iconSize: [28, 38],
    shadowUrl: 'assets/images/marker-shadow.png',
    shadowAnchor: [8, 15],
    shadowSize: [25, 18],
    iconAnchor: [14, 38],
  });

  private selectedIcon = new L.Icon({
    iconUrl: 'assets/images/selected_marker.png',
    iconSize: [28, 38],
    shadowUrl: 'assets/images/marker-shadow.png',
    shadowAnchor: [8, 15],
    shadowSize: [25, 18],
    iconAnchor: [14, 38],
  });

  @Input() inputSyntheseData: GeoJSON;
  @Output() onAreasToggle = new EventEmitter<EventToggle>();

  constructor(
    public mapListService: MapListService,
    private _ms: MapService,
    public formService: SyntheseFormService,
    private _commonService: CommonService,
    private translateService: TranslateService,
    public config: ConfigService
  ) {
    this.SYNTHESE_CONFIG = this.config.SYNTHESE;
    // set a new featureGroup - cluster or not depending of the synthese config
    this.cluserOrSimpleFeatureGroup = this.config.SYNTHESE.ENABLE_LEAFLET_CLUSTER
      ? (L as any).markerClusterGroup()
      : new L.FeatureGroup();
    this.areasEnable =
      this.config.SYNTHESE.AREA_AGGREGATION_ENABLED &&
      this.config.SYNTHESE.AREA_AGGREGATION_BY_DEFAULT;
  }

  ngOnInit() {
    this.leafletDrawOptions.draw.rectangle = true;
    this.leafletDrawOptions.draw.circle = true;
    this.leafletDrawOptions.draw.polyline = false;
    this.leafletDrawOptions.edit.remove = true;
    this.initializeFormWithMapParams();
  }

  private initializeFormWithMapParams() {
    this.formService.searchForm.patchValue({
      format: this.areasEnable ? 'grouped_geom_by_areas' : 'grouped_geom',
    });
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    this.destroy$.unsubscribe();
  }

  ngAfterViewInit() {
    // event from the list
    // On table click, change style layer and zoom
    this.mapListService.onTableClick$.subscribe((id) => {
      const selectedLayers = this.layersDict[id];
      if (selectedLayers) {
        this.toggleStyleFromList(selectedLayers);
        const tempFeatureGroup = new L.FeatureGroup();
        selectedLayers.forEach((layer) => {
          tempFeatureGroup.addLayer(layer);
        });
        this._ms.map.fitBounds(tempFeatureGroup.getBounds(), { maxZoom: 18 });
      } else {
        this._commonService.regularToaster(
          'warning',
          "L'observation selectionnée n'est présente dans aucune maille - passez en mode 'point' pour la localiser"
        );
      }
    });

    // add the featureGroup to the map
    this.cluserOrSimpleFeatureGroup.addTo(this._ms.map);

    // Handle areas button and legend
    if (this.config.SYNTHESE.AREA_AGGREGATION_ENABLED) {
      this.addAreasButton();
      this.onLanguageChange();
      if (this.areasEnable) {
        this.addAreasLegend();
      }
    }
  }

  private onLanguageChange() {
    // don't forget to unsubscribe!
    this.translateService.onLangChange
      .pipe(takeUntil(this.destroy$))
      .subscribe((langChangeEvent: LangChangeEvent) => {
        this.defineI18nMessages();
      });
  }

  private defineI18nMessages() {
    // Define default messages for datatable
    this.translateService
      .get('Synthese.Map.AreasToggleBtn')
      .subscribe((translatedTxt: string[]) => {
        this.areasLabelSwitchBtn.innerText = translatedTxt;
      });
  }

  addAreasButton() {
    const LayerControl = L.Control.extend({
      options: {
        position: 'topright',
      },
      onAdd: (map) => {
        let switchBtnContainer = L.DomUtil.create(
          'div',
          'leaflet-bar custom-control custom-switch leaflet-control-custom synthese-map-areas'
        );

        let switchBtn = L.DomUtil.create('input', 'custom-control-input', switchBtnContainer);
        switchBtn.id = 'toggle-areas-btn';
        switchBtn.type = 'checkbox';
        switchBtn.checked = this.config.SYNTHESE.AREA_AGGREGATION_BY_DEFAULT;
        switchBtn.onclick = () => {
          this.areasEnable = switchBtn.checked;
          this.formService.selectors = this.formService.selectors.set(
            'format',
            switchBtn.checked ? 'grouped_geom_by_areas' : 'grouped_geom'
          );
          this.onAreasToggle.emit(switchBtn.checked ? 'grid' : 'point');

          // Show areas legend if areas toggle button is enable
          if (this.areasEnable) {
            this.addAreasLegend();
          } else {
            this.removeAreasLegend();
            this.enableFitBounds = false;
          }
        };

        this.areasLabelSwitchBtn = L.DomUtil.create(
          'label',
          'custom-control-label',
          switchBtnContainer
        );
        this.areasLabelSwitchBtn.setAttribute('for', 'toggle-areas-btn');
        this.areasLabelSwitchBtn.innerText = this.translateService.instant(
          'Synthese.Map.AreasToggleBtn'
        );

        return switchBtnContainer;
      },
    });

    const map = this._ms.getMap();
    map.addControl(new LayerControl());
  }

  private addAreasLegend() {
    this.areasLegend = new (L.Control.extend({
      options: { position: 'bottomright' },
    }))();

    const vm = this;
    this.areasLegend.onAdd = (map) => {
      let div = L.DomUtil.create('div', 'info legend');
      let grades = this.config['SYNTHESE']['AREA_AGGREGATION_LEGEND_CLASSES']
        .map((legendClass) => legendClass.min)
        .reverse();
      let labels = ["<strong> Nombre <br> d'observations </strong> <br>"];

      // loop through our density intervals and generate a label with a colored square for each interval
      for (var i = 0; i < grades.length; i++) {
        labels.push(
          '<i style="background:' +
            vm.getColor(grades[i] + 1) +
            '"></i> ' +
            grades[i] +
            (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+')
        );
      }
      div.innerHTML = labels.join('<br>');

      return div;
    };

    const map = this._ms.getMap();
    this.areasLegend.addTo(map);
  }

  private removeAreasLegend() {
    const map = this._ms.getMap();
    this.areasLegend.remove(map);
  }

  layerDictCache(idSyntheseList, layer) {
    for (let id of idSyntheseList) {
      id in this.layersDict ? this.layersDict[id].push(layer) : (this.layersDict[id] = [layer]);
    }
  }

  layerEvent(feature, layer, idSyntheseIds) {
    layer.on({
      click: (e) => {
        this.toggleStyleFromMap(feature, layer);
        this.mapListService.mapSelected.next(idSyntheseIds);
        if (this.areasEnable) {
          this.bindAreasPopup(layer, idSyntheseIds);
        }
      },
    });
  }

  onEachFeature(feature, layer) {
    // make a cache a layers in a dict with id key
    this.layerDictCache(feature.properties.observations.id_synthese, layer);
    // set style
    if (this.areasEnable) {
      this.setAreasStyle(layer, feature.properties.observations.id_synthese.length);
    }
    this.layerEvent(feature, layer, feature.properties.observations.id_synthese);
  }

  /**
   *
   */
  clusterCountOverrideFn(cluster) {
    const obsChildCount = cluster
      .getAllChildMarkers()
      .map((layer) => {
        return layer.nb_obs;
      })
      .reduce((previous, next) => previous + next);
    const clusterSize = obsChildCount > 100 ? 'large' : obsChildCount > 10 ? 'medium' : 'small';
    return L.divIcon({
      html: `<div><span>${obsChildCount}</span></div>`,
      className: `marker-cluster marker-cluster-${clusterSize}`,
      iconSize: L.point(40, 40),
    });
  }

  ngOnChanges(change) {
    // clear layerDict cache
    this.layersDict = {};
    // on change delete the previous layer and load the new ones from the geojson data send by the API
    // here we don't use geojson component for performance reasons
    if (this._ms.map) {
      // remove the whole featureGroup to avoid iterate over all its layer
      this._ms.map.removeLayer(this.cluserOrSimpleFeatureGroup);
    }
    if (change && change.inputSyntheseData.currentValue) {
      // regenerate the featuregroup
      this.cluserOrSimpleFeatureGroup = this.config.SYNTHESE.ENABLE_LEAFLET_CLUSTER
        ? (L as any).markerClusterGroup({
            iconCreateFunction: this.clusterCountOverrideFn,
          })
        : new L.FeatureGroup();
      const geojsonLayer = new L.GeoJSON(change.inputSyntheseData.currentValue, {
        pointToLayer: (feature, latlng) => {
          const circleMarker = L.circleMarker(latlng);
          let countObs = feature.properties.observations.id_synthese.length;
          (circleMarker as any).nb_obs = countObs;
          circleMarker.bindTooltip(`${countObs}`, {
            permanent: true,
            direction: 'center',
            offset: L.point({ x: 0, y: 0 }),
            className: 'number-obs',
          });

          return circleMarker;
        },
        onEachFeature: this.onEachFeature.bind(this),
      });
      this.cluserOrSimpleFeatureGroup.addLayer(geojsonLayer);
      this._ms.map.addLayer(this.cluserOrSimpleFeatureGroup);
      // zoom on extend after first search
      if (change.inputSyntheseData.previousValue !== undefined) {
        try {
          // try to fit bound on layer. catch error if no layer in feature group
          if (this.enableFitBounds) {
            this._ms.map.fitBounds(this.cluserOrSimpleFeatureGroup.getBounds());
          } else {
            this.enableFitBounds = true;
          }
        } catch (error) {}
      }
    }
  }

  private setAreasStyle(layer, obsNbr) {
    this.originAreasStyle['fillColor'] = this.getColor(obsNbr);
    layer.setStyle(this.originAreasStyle);
    delete this.originAreasStyle['fillColor'];
  }

  private getColor(obsNbr) {
    let classesNbr = this.config['SYNTHESE']['AREA_AGGREGATION_LEGEND_CLASSES'].length;
    let lastIndex = classesNbr - 1;
    for (let i = 0; i < classesNbr; i++) {
      let legendClass = this.config['SYNTHESE']['AREA_AGGREGATION_LEGEND_CLASSES'][i];
      if (i != lastIndex) {
        if (obsNbr > legendClass.min) {
          return legendClass.color;
        }
      } else {
        return legendClass.color;
      }
    }
  }

  toggleStyleFromMap(feature, layer) {
    // restore initial style
    let originStyle = this.areasEnable ? this.originAreasStyle : this.originDefaultStyle;
    let selectedStyle = this.areasEnable ? this.selectedAreasStyle : this.selectedDefaultStyle;

    if (this.selectedLayers.length > 0) {
      this.selectedLayers.forEach((layer) => {
        (layer as L.GeoJSON).setStyle(originStyle);
      });
    }
    // set selected style

    layer.setStyle(selectedStyle);
    this.selectedLayers = [layer];
  }

  private toggleStyleFromList(currentSelectedLayers) {
    // restore inital style
    let originStyle = this.areasEnable ? this.originAreasStyle : this.originDefaultStyle;
    if (this.selectedLayers.length > 0) {
      this.selectedLayers.forEach((layer) => {
        (layer as L.GeoJSON).setStyle(originStyle);
      });
    }
    // Apply new selected layer
    this.selectedLayers = currentSelectedLayers;

    let selectedStyle = this.areasEnable ? this.selectedAreasStyle : this.selectedDefaultStyle;
    this.selectedLayers.forEach((layer) => {
      (layer as L.GeoJSON).setStyle(selectedStyle);
    });
  }

  private bindAreasPopup(layer, ids) {
    let popupContent = `<b>${ids.length} observation(s)</b>`;
    layer.bindPopup(popupContent).openPopup();
  }

  bindGeojsonForm(geojson) {
    this.formService.searchForm.controls.geoIntersection.setValue(geojson);
    // set the current coord of the geojson to remove layer from filelayer component via the input removeLayer
    this.currentLeafletDrawCoord = geojson;
  }

  onFileLayerLoaded(geojson) {
    this.formService.searchForm.controls.geoIntersection.setValue(geojson);

    if (this.firstFileLayerMessage) {
      this._commonService.translateToaster('success', 'Map.FileLayerInfoSynthese');
    }
    this.firstFileLayerMessage = false;
  }

  deleteControlValue() {
    this.formService.searchForm.controls.geoIntersection.reset();
  }
}

results matching ""

    No results matching ""