From 6a63935bc74a982bde4111a38146cfb0c63b9f8e Mon Sep 17 00:00:00 2001 From: Jules Jean-Louis Date: Fri, 11 Oct 2024 15:03:06 +0200 Subject: [PATCH 1/2] feat: add meshes option on map --- .../tab-geographic-overview.component.html | 1 + .../tab-geographic-overview.component.scss | 39 ++++ .../tab-geographic-overview.component.ts | 215 +++++++++++++++++- 3 files changed, 243 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.html b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.html index 3512b81770..77b2a95394 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.html +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.html @@ -2,5 +2,6 @@ diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.scss b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.scss index e69de29bb2..eb023504a4 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.scss +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.scss @@ -0,0 +1,39 @@ +::ng-deep .leaflet-tooltip-pane .number-obs { + color: rgb(0, 0, 0); + /* font-weight: bold; */ + background: transparent; + border: 0; + box-shadow: none; + /* font-size:2; */ +} +::ng-deep .tab-geographic-overview { + background-color: rgba(255, 255, 255, 0.8); +} +::ng-deep .tab-geographic-overview .custom-control-label { + margin: 0.2rem; +} +::ng-deep .tab-geographic-overview .custom-control-label::before { + top: 0.05rem; +} +::ng-deep .tab-geographic-overview .custom-control-label::after { + top: calc(0.05rem + 2px); +} +::ng-deep .legend { + line-height: 10px; + font-size: 14px; + color: #555; + background-color: white; + opacity: 0.7; + padding: 5px; + border-radius: 4px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); +} +::ng-deep .legend i { + width: 18px; + height: 18px; + float: left; + margin-right: 8px; +} +::ng-deep .legend:hover { + opacity: 1; +} diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts index b20a7bacc9..f70acc9858 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts @@ -6,6 +6,18 @@ import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; import { FeatureCollection } from 'geojson'; import { TaxonSheetService } from '../taxon-sheet.service'; +import { ConfigService } from '@geonature/services/config.service'; +import * as L from 'leaflet'; +import { SyntheseFormService } from '@geonature_common/form/synthese-form/synthese-form.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MapService } from '@geonature_common/map/map.service'; + +interface MapAreasStyle { + color: string; + weight: number; + fillOpacity: number; + fillColor?: string; +} @Component({ standalone: true, @@ -16,11 +28,38 @@ import { TaxonSheetService } from '../taxon-sheet.service'; }) export class TabGeographicOverviewComponent implements OnInit { observations: FeatureCollection | null = null; + areasEnable: boolean; + areasLegend: any; + private areasLabelSwitchBtn; + public featureGroup: any; + styleTabGeoJson: {}; + + mapAreasStyle: MapAreasStyle = { + color: '#FFFFFF', + weight: 0.4, + fillOpacity: 0.8, + }; + + mapAreasStyleActive: MapAreasStyle = { + color: '#FFFFFF', + weight: 0.4, + fillOpacity: 0, + }; + constructor( private _syntheseDataService: SyntheseDataService, private _tss: TaxonSheetService, - public mapListService: MapListService - ) {} + public mapListService: MapListService, + public config: ConfigService, + public formService: SyntheseFormService, + public translateService: TranslateService, + private _ms: MapService + ) { + this.areasEnable = + this.config.SYNTHESE.AREA_AGGREGATION_ENABLED && + this.config.SYNTHESE.AREA_AGGREGATION_BY_DEFAULT; + this.featureGroup = new L.FeatureGroup(); + } ngOnInit() { this._tss.taxon.subscribe((taxon: Taxon | null) => { @@ -28,17 +67,169 @@ export class TabGeographicOverviewComponent implements OnInit { this.observations = null; return; } - this._syntheseDataService - .getSyntheseData( - { - cd_ref: [taxon.cd_ref], - }, - {} - ) - .subscribe((data) => { - // Store geojson - this.observations = data; + const format = this.areasEnable ? 'grouped_geom_by_areas' : 'grouped_geom'; + this.updateTabGeographic(taxon, format); + }); + this.initializeFormWithMapParams(); + } + + updateTabGeographic(taxon: Taxon, format: string) { + this._syntheseDataService + .getSyntheseData( + { + cd_ref: [taxon.cd_ref], + }, + { format } + ) + .subscribe((data) => { + this.observations = data; + + this.styleTabGeoJson = undefined; + + const map = this._ms.getMap(); + + map.eachLayer((layer) => { + if (!(layer instanceof L.TileLayer)) { + map.removeLayer(layer); + } }); + + if (this.observations && this.areasEnable) { + L.geoJSON(this.observations, { + onEachFeature: (feature, layer) => { + const observations = feature.properties.observations; + if (observations && observations.length > 0) { + const obsCount = observations.length; + this.setAreasStyle(layer as L.Path, obsCount); + } + }, + }).addTo(map); + } + }); + } + + private initializeFormWithMapParams() { + this.formService.searchForm.patchValue({ + format: this.areasEnable ? 'grouped_geom_by_areas' : 'grouped_geom', }); } + + ngAfterViewInit() { + this.addAreasButton(); + if (this.areasEnable) { + this.addAreasLegend(); + } + } + + 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 tab-geographic-overview' + ); + + 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; + const format = switchBtn.checked ? 'grouped_geom_by_areas' : 'grouped_geom'; + + this._tss.taxon.subscribe((taxon: Taxon | null) => { + if (taxon) { + this.updateTabGeographic(taxon, format); + } + }); + + if (this.areasEnable) { + this.addAreasLegend(); + } else { + this.removeAreasLegend(); + } + }; + + 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() { + if (this.areasLegend) return; + this.areasLegend = new (L.Control.extend({ + options: { position: 'bottomright' }, + }))(); + + this.areasLegend.onAdd = (map: L.Map): HTMLElement => { + let div: HTMLElement = L.DomUtil.create('div', 'info legend'); + let grades: number[] = this.config['SYNTHESE']['AREA_AGGREGATION_LEGEND_CLASSES'] + .map((legendClass: { min: number; color: string }) => legendClass.min) + .reverse(); + let labels: string[] = [" Nombre
d'observations

"]; + + for (let i = 0; i < grades.length; i++) { + labels.push( + ' ' + + grades[i] + + (grades[i + 1] ? '–' + grades[i + 1] + '
' : '+') + ); + } + div.innerHTML = labels.join('
'); + + return div; + }; + + const map = this._ms.getMap(); + this.areasLegend.addTo(map); + } + + private removeAreasLegend() { + if (this.areasLegend) { + const map = this._ms.getMap(); + map.removeControl(this.areasLegend); + this.areasLegend = null; + } + } + + private setAreasStyle(layer: L.Path, obsNbr: number) { + this.mapAreasStyle['fillColor'] = this.getColor(obsNbr); + layer.setStyle(this.mapAreasStyle); + delete this.mapAreasStyle['fillColor']; + this.styleTabGeoJson = this.mapAreasStyleActive; + } + + private getColor(obsNbr: number) { + 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; + } + } + } } From 34195ba6c443afc50bd4a36cc68ea9812d7d5016 Mon Sep 17 00:00:00 2001 From: Jules Jean-Louis Date: Fri, 11 Oct 2024 15:50:29 +0200 Subject: [PATCH 2/2] chore: remove unused variable --- .../tab-geographic-overview.component.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts index f70acc9858..f1ff9f379c 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-geographic-overview/tab-geographic-overview.component.ts @@ -30,8 +30,7 @@ export class TabGeographicOverviewComponent implements OnInit { observations: FeatureCollection | null = null; areasEnable: boolean; areasLegend: any; - private areasLabelSwitchBtn; - public featureGroup: any; + private _areasLabelSwitchBtn; styleTabGeoJson: {}; mapAreasStyle: MapAreasStyle = { @@ -58,7 +57,6 @@ export class TabGeographicOverviewComponent implements OnInit { this.areasEnable = this.config.SYNTHESE.AREA_AGGREGATION_ENABLED && this.config.SYNTHESE.AREA_AGGREGATION_BY_DEFAULT; - this.featureGroup = new L.FeatureGroup(); } ngOnInit() { @@ -154,13 +152,13 @@ export class TabGeographicOverviewComponent implements OnInit { } }; - this.areasLabelSwitchBtn = L.DomUtil.create( + this._areasLabelSwitchBtn = L.DomUtil.create( 'label', 'custom-control-label', switchBtnContainer ); - this.areasLabelSwitchBtn.setAttribute('for', 'toggle-areas-btn'); - this.areasLabelSwitchBtn.innerText = this.translateService.instant( + this._areasLabelSwitchBtn.setAttribute('for', 'toggle-areas-btn'); + this._areasLabelSwitchBtn.innerText = this.translateService.instant( 'Synthese.Map.AreasToggleBtn' );