diff --git a/projects/arlas-components/assets/i18n/en.json b/projects/arlas-components/assets/i18n/en.json index d3915041..198bf919 100644 --- a/projects/arlas-components/assets/i18n/en.json +++ b/projects/arlas-components/assets/i18n/en.json @@ -75,5 +75,14 @@ "None": "None", "Fit the whole thumbnail to the tile": "Fit the whole thumbnail to the tile", "Fit the thumbnail's width to the tile": "Fit the thumbnail's width to the tile", - "Fit the thumbnail's height to the tile": "Fit the thumbnail's height to the tile" + "Fit the thumbnail's height to the tile": "Fit the thumbnail's height to the tile", + "Enter coordinates in decimal or sexagesimal degrees": "Enter coordinates in decimal or sexagesimal degrees.", + "BBox generator": "Generate a bbox", + "First corner": "First corner", + "Second corner": "Second corner", + "You must enter a coordinate": "Enter a coordinate", + "Enter a coordinate in decimal (1.1) or sexagesimal (1° 6' 3\")": "Enter a coordinate in decimal (1.1) or sexagesimal (1° 6' 3\")", + "Decimal: 1.1 or Sexagesimal 1°6'3\" coordinate": "Decimal: 1.1 or Sexagesimal: 1°6'3\" coordinate", + "Both corners have the same latitudes, change one of them.": "Both corners have the same latitudes, modify one of them.", + "Generate AOI": "Generate the bbox" } \ No newline at end of file diff --git a/projects/arlas-components/assets/i18n/fr.json b/projects/arlas-components/assets/i18n/fr.json index e83d0a06..9be8fda0 100644 --- a/projects/arlas-components/assets/i18n/fr.json +++ b/projects/arlas-components/assets/i18n/fr.json @@ -75,5 +75,14 @@ "None": "Aucune", "Fit the whole thumbnail to the tile": "Ajuster la taille de la vignette à la tuile", "Fit the thumbnail's width to the tile": "Ajuster la largeur de la vignette à la tuile", - "Fit the thumbnail's height to the tile": "Ajuster la hauteur de la vignette à la tuile" + "Fit the thumbnail's height to the tile": "Ajuster la hauteur de la vignette à la tuile", + "Enter coordinates in decimal or sexagesimal degrees": "Saisir les coordonnées en degrés décimaux ou sexagésimaux.", + "BBox generator": "Générer une bbox", + "First corner": "Premier coin", + "Second corner": "Deuxième coin", + "You must enter a coordinate": "Saisir une coordonnée", + "Enter a coordinate in decimal (1.1) or sexagesimal (1° 6' 3\")": "Saisir une coordonnée en décimal (1.1) ou en sexagésimal (1° 6' 3\")", + "Decimal: 1.1 or Sexagesimal 1°6'3\" coordinate": "Coordonnée en décimal: 1.1 or sexagésimal: 1°6'3\"", + "Both corners have the same latitudes, modify one of them.": "Les deux coins ont les mêmes latitudes, modifiez l'un d'entre eux.", + "Generate AOI": "Générer la bbox" } \ No newline at end of file diff --git a/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.html b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.html new file mode 100644 index 00000000..e8763a9b --- /dev/null +++ b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.html @@ -0,0 +1,60 @@ +
+
+ {{'BBox generator' | translate}} + clear +
+
+
+ info + {{DESCRIPTION | translate}} +
+
+
{{'First corner' | translate}}
+
+ + {{'Latitude'}} + + {{getErrorMessage(bboxForm.firstCorner.latitude) | translate}} + +
+
+ + {{'Longitude'}} + + {{getErrorMessage(bboxForm.firstCorner.longitude) | translate}} + +
+
+
+
{{'Second corner' | translate}}
+
+ + {{'Latitude'}} + + {{getErrorMessage(bboxForm.secondCorner.latitude) | translate}} + +
+
+ + {{'Longitude'}} + + {{getErrorMessage(bboxForm.secondCorner.longitude) | translate}} + +
+ + +
+
+ report + {{getErrorMessage(bboxForm) | translate}} +
+
+
+ + +
+
\ No newline at end of file diff --git a/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.scss b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.scss new file mode 100644 index 00000000..e838714c --- /dev/null +++ b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.scss @@ -0,0 +1,68 @@ +.wrapper { + padding: 5px; + border-radius: 3px; + background-color: white; + font-size: 16px; + min-width: 365px; + .title-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 600; + } + .content { + display: flex; + flex-direction: column; + .description { + margin: 5px 0; + display: flex; + align-items: center; + font-size: 14px; + color: rgb(146, 146, 146); + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + margin-right: 5px; + } + } + .section { + .title { + margin-bottom: 5px; + } + display: flex; + flex-direction: column; + .coordinate { + width: 100%; + mat-form-field { + width: 100%; + } + .input { + font-size: 14px; + } + } + } + + .errors { + mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + margin-right: 5px; + color: #f44336; + } + margin-bottom: 10px; + display: flex; + align-items: center; + font-size: 12px; + } + } + + .actions { + display: flex; + justify-content: end; + .create-action { + margin-left: 5px; + } + } +} \ No newline at end of file diff --git a/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.ts b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.ts new file mode 100644 index 00000000..c6c3362f --- /dev/null +++ b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.component.ts @@ -0,0 +1,191 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AfterViewInit, ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Observable, merge, mergeMap, of } from 'rxjs'; +import { MapboxAoiDrawService } from '../mapgl/draw/draw.service'; +import { Corner } from '../mapgl/draw/draw.models'; + +@Component({ + selector: 'arlas-bbox-generator', + templateUrl: './bbox-generator.component.html', + styleUrls: ['./bbox-generator.component.scss'] +}) +export class BboxGeneratorComponent implements OnInit, AfterViewInit { + public DESCRIPTION = 'Enter coordinates in decimal or sexagesimal degrees'; + public bboxForm: BboxFormGroup; + public placeHolder = 'Decimal: 1.1 or Sexagesimal 1°6\'3" coordinate'; + public constructor( + private drawService: MapboxAoiDrawService, + private cdr: ChangeDetectorRef, + @Inject(MAT_DIALOG_DATA) public data: { + initCorner: Corner; + }, + public dialogRef: MatDialogRef,) { + } + + public ngOnInit(): void { + if (!!this.data && !this.data.initCorner) { + this.data.initCorner = { + lat: 32, + lng: -8 + }; + } + this.bboxForm = new BboxFormGroup(this.data.initCorner); + } + public ngAfterViewInit(): void { + this.cdr.detectChanges(); + } + + public getErrorMessage(formControl: FormControl | FormGroup) { + if (formControl.hasError('required')) { + return 'You must enter a coordinate'; + } else if ((formControl as BboxFormGroup).latitudeErrors) { + return 'Both corners have the same latitudes, change one of them.'; + } + return formControl.hasError('pattern') ? 'Enter a coordinate in decimal (1.1) or sexagesimal (1° 6\' 3")' : ''; + } + + public generateBbox() { + this.drawService.drawBbox(this.bboxForm.getFirstCorner(), this.bboxForm.getSecondCorner()); + this.dialogRef.close(); + } + +} + +export class BboxFormGroup extends FormGroup { + + private firstCornerLatitude; + private secondCornerLatitude; + public firstCorner: PointFormGroup; + public secondCorner: PointFormGroup; + public latitudeErrors = false; + public constructor(corner: Corner) { + const firstCorner = new PointFormGroup(corner.lat - 0.5, corner.lng - 0.5); + const secondCorner = new PointFormGroup(corner.lat + 0.5, corner.lng + 0.5); + super({ + firstCorner, + secondCorner + }); + this.firstCorner = firstCorner; + this.secondCorner = secondCorner; + + this.firstCorner.latitude.valueChanges.subscribe(v => { + this.firstCornerLatitude = v; + this.secondCornerLatitude = this.secondCorner.latitude.value; + if (this.secondCornerLatitude !== undefined) { + if (this.parse(this.firstCornerLatitude) === this.parse(this.secondCornerLatitude)) { + this.latitudeErrors = true; + } else { + this.latitudeErrors = false; + } + } + }); + + this.secondCorner.latitude.valueChanges.subscribe(v => { + this.secondCornerLatitude = v; + this.firstCornerLatitude = this.firstCorner.latitude.value; + if (this.firstCornerLatitude !== undefined) { + if (this.parse(this.firstCornerLatitude) === this.parse(this.secondCornerLatitude)) { + this.latitudeErrors = true; + } else { + this.latitudeErrors = false; + } + } + }); + } + + public getFirstCorner(): Corner { + this.parse(this.firstCorner.latitude.value); + this.parse(this.firstCorner.longitude.value); + return { + lat: this.parse(this.firstCorner.latitude.value), + lng: this.parse(this.firstCorner.longitude.value) + }; + } + + public getSecondCorner(): Corner { + return { + lat: this.parse(this.secondCorner.latitude.value), + lng: this.parse(this.secondCorner.longitude.value) + }; + } + + private parse(value: string) { + // eslint-disable-next-line max-len + const coordinatesRegex = '^(?[+-]?([0-9]*[.])?[0-9]+)$|^(?(-?)[0-9]+)°[ ]*((?[0-9]+)\'[ ]*((?[0-9]+)\")?)?$'; + const parsedCoordinates = (String(value)).match(coordinatesRegex); + if (parsedCoordinates && parsedCoordinates.groups) { + const groups = parsedCoordinates.groups; + if (groups.decimal) { + return +groups.decimal; + } else { + const degrees = +groups.degrees; + const minutes = +groups.minutes; + const seconds = +groups.seconds; + return this.dmsToDd(degrees, minutes, seconds); + } + } + } + + private dmsToDd(degrees: number, minutes: number, seconds: number) { + const isNegative = (degrees < 0); + if (!minutes) { + minutes = 0; + } + if (!seconds) { + seconds = 0; + } + const dd = Math.abs(degrees) + minutes / 60 + seconds / 3600; + return isNegative ? -dd : dd; + } +} + +export class PointFormGroup extends FormGroup { + + public latitude: FormControl; + public longitude: FormControl; + + public latitudeChanges$: Observable; + public longitudesChanges$: Observable; + + public constructor(initLat: number, initLng: number) { + // eslint-disable-next-line max-len + const coordinatesRegex = '^(?[+-]?([0-9]*[.])?[0-9]+)$|^(?(-?)[0-9]+)°[ ]*((?[0-9]+)\'[ ]*((?[0-9]+)\")?)?$'; + const latitude = new FormControl(String(initLat), [ + Validators.required, + Validators.pattern(coordinatesRegex) + ]); + const longitude = new FormControl(String(initLng), [ + Validators.required, + Validators.pattern(coordinatesRegex), + ]); + super({ + latitude, + longitude + }); + this.latitude = latitude; + this.longitude = longitude; + } + +} + + diff --git a/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.module.ts b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.module.ts new file mode 100644 index 00000000..0e4703bf --- /dev/null +++ b/projects/arlas-components/src/lib/components/bbox-generator/bbox-generator.module.ts @@ -0,0 +1,50 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { BrowserModule } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; +import { BboxGeneratorComponent } from './bbox-generator.component'; +import { NgModule } from '@angular/core'; +import { MatInputModule } from '@angular/material/input'; + +@NgModule({ + imports: [ + CommonModule, + BrowserModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatIconModule, + MatButtonModule, + MatInputModule, + ReactiveFormsModule, + TranslateModule + ], + declarations: [BboxGeneratorComponent], + exports: [BboxGeneratorComponent] +}) +export class BboxGeneratorModule { + +} diff --git a/projects/arlas-components/src/lib/components/mapgl/draw/draw.models.ts b/projects/arlas-components/src/lib/components/mapgl/draw/draw.models.ts index e9f1a750..5b8a81b0 100644 --- a/projects/arlas-components/src/lib/components/mapgl/draw/draw.models.ts +++ b/projects/arlas-components/src/lib/components/mapgl/draw/draw.models.ts @@ -32,3 +32,15 @@ export interface EditionState { isDrawing: boolean; isEditing: boolean; } + +export interface Corner { + lat: number; + lng: number; +} + +export interface BboxDrawCommand { + east: number; + west: number; + north: number; + south: number; +} diff --git a/projects/arlas-components/src/lib/components/mapgl/draw/draw.service.ts b/projects/arlas-components/src/lib/components/mapgl/draw/draw.service.ts index bf9b5a39..ff9b40d4 100644 --- a/projects/arlas-components/src/lib/components/mapgl/draw/draw.service.ts +++ b/projects/arlas-components/src/lib/components/mapgl/draw/draw.service.ts @@ -25,7 +25,7 @@ import { Feature, FeatureCollection, lineString } from '@turf/helpers'; import bbox from '@turf/bbox'; import length from '@turf/length'; import { Subject } from 'rxjs'; -import { AoiDimensions, EditionState } from './draw.models'; +import { AoiDimensions, BboxDrawCommand, Corner, EditionState } from './draw.models'; @Injectable() export class MapboxAoiDrawService { @@ -38,6 +38,9 @@ export class MapboxAoiDrawService { private editAoiSource = new Subject(); public editAoi$ = this.editAoiSource.asObservable(); + private drawBboxSource = new Subject(); + public drawBbox$ = this.drawBboxSource.asObservable(); + public bboxEditionState: EditionState; public polygonEditionState: EditionState; @@ -54,6 +57,19 @@ export class MapboxAoiDrawService { }; } + public drawBbox(fCorner: Corner, sCorner: Corner) { + const west = Math.min(fCorner.lng, sCorner.lng); + const east = Math.max(fCorner.lng, sCorner.lng); + const south = Math.min(fCorner.lat, sCorner.lat); + const north = Math.max(fCorner.lat, sCorner.lat); + this.drawBboxSource.next({ + west, + east, + south, + north + }); + } + public enableBboxEdition() { this.bboxEditionState.enabled = true; this.bboxEditionState.isDrawing = false; diff --git a/projects/arlas-components/src/lib/components/mapgl/mapgl.component.ts b/projects/arlas-components/src/lib/components/mapgl/mapgl.component.ts index 55763b4e..72fcc423 100644 --- a/projects/arlas-components/src/lib/components/mapgl/mapgl.component.ts +++ b/projects/arlas-components/src/lib/components/mapgl/mapgl.component.ts @@ -49,7 +49,7 @@ import StaticMode from '@mapbox/mapbox-gl-draw-static-mode'; import * as styles from './model/theme'; import { getLayerName } from '../componentsUtils'; import { MapboxAoiDrawService } from './draw/draw.service'; -import { AoiDimensions } from './draw/draw.models'; +import { AoiDimensions, BboxDrawCommand } from './draw/draw.models'; import { BasemapStyle } from './basemaps/basemap.config'; import { MapboxBasemapService } from './basemaps/basemap.service'; import { ArlasBasemaps } from './basemaps/basemaps'; @@ -443,11 +443,17 @@ export class MapglComponent implements OnInit, AfterViewInit, OnChanges, OnDestr private drawSelectionChanged = false; private finishDrawTooltip: HTMLElement; private aoiEditSubscription: Subscription; + private drawBboxSubscription: Subscription; public constructor(private http: HttpClient, private drawService: MapboxAoiDrawService, private basemapService: MapboxBasemapService, private _snackBar: MatSnackBar, private translate: TranslateService) { this.aoiEditSubscription = this.drawService.editAoi$.subscribe(ae => this.onAoiEdit.emit(ae)); + this.drawBboxSubscription = this.drawService.drawBbox$.subscribe({ + next: (bboxDC: BboxDrawCommand) => { + this.drawBbox(bboxDC.east, bboxDC.south, bboxDC.west, bboxDC.north); + } + }); } @@ -1389,6 +1395,9 @@ export class MapglComponent implements OnInit, AfterViewInit, OnChanges, OnDestr if (!!this.offlineBasemapChangeSubscription) { this.offlineBasemapChangeSubscription.unsubscribe(); } + if (!!this.drawBboxSubscription) { + this.drawBboxSubscription.unsubscribe(); + } } public selectFeaturesByCollection(features: Array, collection: string) { @@ -1668,35 +1677,7 @@ export class MapglComponent implements OnInit, AfterViewInit, OnChanges, OnDestr const north = Math.max(startlat, endlat); const east = Math.max(startlng, endlng); const south = Math.min(startlat, endlat); - const coordinates = [[ - [east, south], - [east, north], - [west, north], - [west, south], - [east, south], - ]]; - const polygonGeojson = { - type: 'Feature', - properties: { - source: 'bbox' - }, - geometry: { - type: 'Polygon', - coordinates: coordinates - } - }; - const geoboxdata = Object.assign({}, this.emptyData); - geoboxdata.features = []; - if (this.drawData && this.drawData.features && this.drawData.features.length > 0) { - this.drawData.features.forEach(df => geoboxdata.features.push(df)); - } - geoboxdata.features.push(polygonGeojson); - /** This allows to keep the drawn box on the map. It will be overriden in ngOnChanges `changes['drawData']` */ - this.drawService.addFeatures(geoboxdata, /** deleteOld */ true); - this.onAoiChanged.next(geoboxdata); - this.isDrawingBbox = false; - this.drawService.disableBboxEdition(); - this.drawService.endDimensionsEmission(); + this.drawBbox(east, south, west, north); if (this.box) { this.box.parentNode.removeChild(this.box); this.box = undefined; @@ -1704,6 +1685,39 @@ export class MapglComponent implements OnInit, AfterViewInit, OnChanges, OnDestr } } + + private drawBbox(east, south, west, north) { + const coordinates = [[ + [east, south], + [east, north], + [west, north], + [west, south], + [east, south], + ]]; + const polygonGeojson = { + type: 'Feature', + properties: { + source: 'bbox' + }, + geometry: { + type: 'Polygon', + coordinates: coordinates + } + }; + const geoboxdata = Object.assign({}, this.emptyData); + geoboxdata.features = []; + if (this.drawData && this.drawData.features && this.drawData.features.length > 0) { + this.drawData.features.forEach(df => geoboxdata.features.push(df)); + } + geoboxdata.features.push(polygonGeojson); + /** This allows to keep the drawn box on the map. It will be overriden in ngOnChanges `changes['drawData']` */ + this.drawService.addFeatures(geoboxdata, /** deleteOld */ true); + this.onAoiChanged.next(geoboxdata); + this.isDrawingBbox = false; + this.drawService.disableBboxEdition(); + this.drawService.endDimensionsEmission(); + } + private setStrokeLayoutVisibility(layerId: string, visibility: string): void { const layer = this.layersMap.get(layerId); if (layer.type === 'fill') { diff --git a/projects/arlas-components/src/public-api.ts b/projects/arlas-components/src/public-api.ts index 674adb2e..2b20a308 100644 --- a/projects/arlas-components/src/public-api.ts +++ b/projects/arlas-components/src/public-api.ts @@ -47,6 +47,8 @@ export { MapglLegendModule } from './lib/components/mapgl-legend/mapgl-legend.mo export { MapglSettingsModule } from './lib/components/mapgl-settings/mapgl-settings.module'; export { MetricComponent } from './lib/components/metric/metric.component'; export { MetricModule } from './lib/components/metric/metric.module'; +export { BboxGeneratorComponent } from './lib/components/bbox-generator/bbox-generator.component'; +export { BboxGeneratorModule } from './lib/components/bbox-generator/bbox-generator.module'; export { PowerbarsComponent } from './lib/components/powerbars/powerbars.component'; export { PowerbarsModule } from './lib/components/powerbars/powerbars.module'; export {