Skip to content

Commit f1d6844

Browse files
Andrea BarbassoFrancescoMolinaro
authored andcommitted
Merged in task/ux-plus-2023_02_x/UXP-206 (pull request #26)
Task/ux plus 2023 02 x/UXP-206 Approved-by: Francesco Molinaro
2 parents 83a591b + 1a67bd7 commit f1d6844

7 files changed

Lines changed: 95 additions & 104 deletions

File tree

src/app/core/services/location.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('LocationService', () => {
3232
expect(service.isDecimalCoordinateString('45,45,45')).toBeFalse(); // invalid pattern, wrong array size
3333
expect(service.isDecimalCoordinateString('.0,.0')).toBeFalse(); // valid numbers, but invalid pattern
3434
expect(service.isDecimalCoordinateString('45.000,45.000')).toBeTrue();
35-
expect(service.isDecimalCoordinateString('45.000, 45.000')).toBeFalse(); // it contains a space
35+
expect(service.isDecimalCoordinateString('45.000, 45.000')).toBeTrue(); // it contains a space but it can be trimmed
3636
expect(service.isDecimalCoordinateString('200,200')).toBeTrue(); // invalid numbers, but valid pattern
3737
});
3838

src/app/core/services/location.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export enum LocationErrorCodes {
2525
API_ERROR = 'api-error',
2626
}
2727

28-
const IS_DD_COORDINATE_PAIR_REGEXP = /^\d+\.?\d*,\d+\.?\d*$/;
28+
const IS_DD_COORDINATE_PAIR_REGEXP = /^\d+\.?\d*,\s?\d+\.?\d*$/;
2929
const IS_SG_COORDINATE_PAIR_REGEXP = /^[NS] *\d+° *\d+['] *\d+(?:"||\.\d+),? *[EW] *\d+° *\d+['] *\d+(?:"||\.\d+)|\d+° *\d+['] *\d+(?:"||\.\d+) *[NS],? *\d+° *\d+['] *\d+(?:"||\.\d+) *[EW]$/;
3030

3131
const NOMINATIM_RESPONSE_FORMAT = 'jsonv2';
@@ -176,8 +176,8 @@ export class LocationService {
176176
public isValidCoordinateString(coordinateString: string): boolean {
177177
if (this.isDecimalCoordinateString(coordinateString)) {
178178
const coordinateArray = coordinateString.split(',');
179-
const latitude = parseFloat(coordinateArray[0]);
180-
const longitude = parseFloat(coordinateArray[1]);
179+
const latitude = parseFloat(coordinateArray[0].trim());
180+
const longitude = parseFloat(coordinateArray[1].trim());
181181
return !isNaN(latitude) && !isNaN(longitude) && this.isValidDecimalCoordinatePair(latitude, longitude);
182182
} else {
183183
return false;

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/metadataGroup/googlemaps-group/googlemaps-group.component.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
77
import { TranslateLoaderMock } from '../../../../../../../../shared/mocks/translate-loader.mock';
88
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
99
import { LoadMoreService } from '../../../../../../../services/load-more.service';
10-
import { GooglemapsComponent } from '../../../../../../../../shared/googlemaps/googlemaps.component';
1110
import { NO_ERRORS_SCHEMA } from '@angular/core';
1211
import { ConfigurationDataService } from '../../../../../../../../core/data/configuration-data.service';
1312
import { createSuccessfulRemoteDataObject$ } from '../../../../../../../../shared/remote-data.utils';
13+
import { HttpClientTestingModule } from '@angular/common/http/testing';
1414

1515
describe('GooglemapsGroupComponent', () => {
1616
let component: GooglemapsGroupComponent;
@@ -80,7 +80,9 @@ describe('GooglemapsGroupComponent', () => {
8080
provide: TranslateLoader,
8181
useClass: TranslateLoaderMock
8282
}
83-
}), BrowserAnimationsModule],
83+
}),
84+
BrowserAnimationsModule,
85+
HttpClientTestingModule],
8486
providers: [
8587
{ provide: 'fieldProvider', useValue: mockField },
8688
{ provide: 'itemProvider', useValue: testItem },
@@ -91,8 +93,8 @@ describe('GooglemapsGroupComponent', () => {
9193
],
9294
schemas: [NO_ERRORS_SCHEMA],
9395
declarations: [
94-
GooglemapsGroupComponent,
95-
GooglemapsComponent]
96+
GooglemapsGroupComponent
97+
]
9698
})
9799
.compileComponents();
98100

src/app/shared/googlemaps/googlemaps.component.spec.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { By } from '@angular/platform-browser';
44
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
55
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
66
import { GooglemapsComponent } from './googlemaps.component';
7+
import { LocationService } from '../../core/services/location.service';
8+
import { HttpClientTestingModule } from '@angular/common/http/testing';
79

810
describe('GooglemapsComponent', () => {
911

1012
let component: GooglemapsComponent;
11-
1213
let fixture: ComponentFixture<GooglemapsComponent>;
1314

1415
const coordinates = '@41.3455,456.67';
@@ -19,17 +20,29 @@ describe('GooglemapsComponent', () => {
1920

2021
const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['valid-googlemap-key'] });
2122

23+
const locationService = jasmine.createSpyObj('locationService', {
24+
findPlaceCoordinates: jasmine.createSpy('findPlaceCoordinates'),
25+
findPlaceAndDecimalCoordinates: jasmine.createSpy('findPlaceAndDecimalCoordinates'),
26+
searchByCoordinates: jasmine.createSpy('searchByCoordinates'),
27+
isValidDecimalCoordinatePair: jasmine.createSpy('isValidDecimalCoordinatePair'),
28+
isDecimalCoordinateString: jasmine.createSpy('isDecimalCoordinateString'),
29+
isSexagesimalCoordinateString: jasmine.createSpy('isSexagesimalCoordinateString'),
30+
isValidCoordinateString: jasmine.createSpy('isValidCoordinateString'),
31+
parseCoordinates: jasmine.createSpy('parseCoordinates'),
32+
});
33+
2234
beforeEach(async () => {
2335
await TestBed.configureTestingModule({
36+
imports: [ HttpClientTestingModule ],
2437
declarations: [ GooglemapsComponent ],
2538
providers: [
26-
{ provide: ConfigurationDataService, useValue: configurationDataService },
39+
{ provide: ConfigurationDataService, useValue: configurationDataService },
40+
{ provide: LocationService, useValue: locationService }
2741
]
2842
})
29-
.compileComponents();
43+
.compileComponents();
3044
});
3145

32-
3346
beforeEach(() => {
3447
fixture = TestBed.createComponent(GooglemapsComponent);
3548
component = fixture.componentInstance;

src/app/shared/googlemaps/googlemaps.component.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getFirstCompletedRemoteData } from '../../core/shared/operators';
66
import { isNotEmpty } from '../empty.util';
77
import { RemoteData } from '../../core/data/remote-data';
88
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
9+
import { LocationService } from '../../core/services/location.service';
910

1011
@Component({
1112
selector: 'ds-googlemaps',
@@ -48,6 +49,7 @@ export class GooglemapsComponent implements OnInit {
4849
private renderer: Renderer2,
4950
private configService: ConfigurationDataService,
5051
private cdr: ChangeDetectorRef,
52+
private locationService: LocationService,
5153
) {
5254
}
5355

@@ -85,14 +87,26 @@ export class GooglemapsComponent implements OnInit {
8587
* Set latitude and longitude when metadata has an address
8688
*/
8789
setLatAndLongFromAddress() {
88-
return new Promise((reslove, reject) => {
90+
return new Promise((resolve, reject) => {
8991
new google.maps.Geocoder().geocode({ 'address': this.coordinates }, (results, status) => {
9092
if (status === google.maps.GeocoderStatus.OK) {
9193
this.latitude = results[0].geometry.location.lat().toString();
9294
this.longitude = results[0].geometry.location.lng().toString();
93-
reslove(1);
95+
resolve(1);
9496
} else {
95-
reject(1);
97+
console.error('Error from Geocoder API: ', results, status);
98+
try {
99+
const coordinates = this.locationService.parseCoordinates(this.coordinates);
100+
if (coordinates) {
101+
this.latitude = coordinates.latitude.toString();
102+
this.longitude = coordinates.longitude.toString();
103+
resolve(1);
104+
} else {
105+
reject(1);
106+
}
107+
} catch (e) {
108+
reject(1);
109+
}
96110
}
97111
});
98112
});

src/app/shared/open-street-map/open-street-map.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
[leafletLayers]="leafletLayers">
88
</div>
99
<div *ngIf="showDisplayName" class="my-1 text-muted">{{ displayName$ | async }}</div>
10-
<div *ngIf="(invalidLocationErrorCode | async) as errorCode"
10+
<div *ngIf="((coordinates$ | async) === null) && (invalidLocationErrorCode | async) as errorCode"
1111
class="text-danger">{{ 'location.error.' + errorCode | translate }}
1212
</div>
Lines changed: 50 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { Component, ElementRef, Input, OnInit } from '@angular/core';
2-
import {
3-
LocationDDCoordinates,
4-
LocationErrorCodes,
5-
LocationPlace,
6-
LocationService
7-
} from '../../core/services/location.service';
8-
import { filter, map, tap } from 'rxjs/operators';
2+
import { LocationService, LocationDDCoordinates, LocationPlace, LocationErrorCodes } from '../../core/services/location.service';
93
import { BehaviorSubject, Observable } from 'rxjs';
4+
import { filter, map, tap } from 'rxjs/operators';
105
import { TranslateService } from '@ngx-translate/core';
116
import { isNotEmpty } from '../empty.util';
12-
import { icon, latLng, LatLng, Layer, MapOptions, marker, tileLayer } from 'leaflet';
7+
import { icon, LatLng, latLng, Layer, MapOptions, marker, tileLayer } from 'leaflet';
138

149
@Component({
1510
selector: 'ds-open-street-map',
@@ -66,7 +61,7 @@ export class OpenStreetMapComponent implements OnInit {
6661
/**
6762
* Contains error codes from the location service
6863
*/
69-
invalidLocationErrorCode: BehaviorSubject<string> = new BehaviorSubject(undefined);
64+
invalidLocationErrorCode = new BehaviorSubject<string>(undefined);
7065

7166
/**
7267
* The place to be shown in the map
@@ -76,7 +71,7 @@ export class OpenStreetMapComponent implements OnInit {
7671
/**
7772
* The styles that are being applied to the map container
7873
*/
79-
mapStyle: {[key: string]: string} = {};
74+
mapStyle: { [key: string]: string } = {};
8075

8176
/**
8277
* The center of the map
@@ -96,11 +91,7 @@ export class OpenStreetMapComponent implements OnInit {
9691
/**
9792
* The options for the map
9893
*/
99-
leafletOptions: MapOptions = {
100-
// attribution is still needed
101-
// attributionControl: false,
102-
zoomControl: this.showControlsZoom
103-
};
94+
leafletOptions: MapOptions = { zoomControl: this.showControlsZoom };
10495

10596
constructor(
10697
protected translateService: TranslateService,
@@ -109,104 +100,75 @@ export class OpenStreetMapComponent implements OnInit {
109100
}
110101

111102
ngOnInit(): void {
112-
113103
this.mapStyle = {
114104
width: this.width || '100%',
115105
height: this.height || `${(+this.width || this.elementRef.nativeElement.parentElement.offsetWidth) / 2}px`
116106
};
117107

118108
this.coordinates$ = this.place.asObservable().pipe(
119-
filter((place) => isNotEmpty(place)),
120-
map((place) => place.coordinates),
109+
filter(isNotEmpty),
110+
map(place => place.coordinates),
121111
tap(coordinates => this.setCenterAndPointer(coordinates))
122112
);
123113

124114
this.displayName$ = this.place.asObservable().pipe(
125-
filter((place) => isNotEmpty(place)),
126-
map((place) => place.displayName),
115+
filter(isNotEmpty),
116+
map(place => place.displayName)
127117
);
128118

129-
const position = this.coordinates; // this may contain a pair or coordinates, a POI, or an address
119+
const position = this.coordinates;
130120

131121
if (this.locationService.isDecimalCoordinateString(position)) {
132-
133-
// Validate the coordinates, then retrieve the location name
134-
135-
if (this.locationService.isValidCoordinateString(position)) {
136-
const coordinates = this.locationService.parseCoordinates(position);
137-
this.locationService.searchByCoordinates(coordinates).subscribe({
138-
next: (displayName) => {
139-
const place: LocationPlace = {
140-
coordinates: coordinates,
141-
displayName: displayName, // Show the name retrieved from Nominatim
142-
};
143-
this.place.next(place);
144-
},
145-
error: (err) => {
146-
// show the map centered on provided coordinates despite the possibility to retrieve a description for the place
147-
const place: LocationPlace = {
148-
coordinates: coordinates,
149-
};
150-
this.place.next(place);
151-
if (err.message === LocationErrorCodes.API_ERROR) {
152-
console.error(err.message);
153-
} else {
154-
console.warn(err.message);
155-
}
156-
},
157-
});
158-
} else {
159-
console.error(`Invalid coordinates: "${position}"`);
160-
this.invalidLocationErrorCode.next(LocationErrorCodes.INVALID_COORDINATES);
161-
}
162-
122+
this.handleDecimalCoordinates(position);
163123
} else if (this.locationService.isSexagesimalCoordinateString(position)) {
164-
165-
// Retrieve the decimal coordinates and the place name for the provided coordinates
166-
167-
this.locationService.findPlaceAndDecimalCoordinates(position).subscribe({
168-
next: (place) => {
169-
this.place.next(place);
170-
},
171-
error: (err) => {
172-
this.invalidLocationErrorCode.next(err.message); // either INVALID_COORDINATES or API_ERROR
173-
if (err.message === LocationErrorCodes.API_ERROR) {
174-
console.error(err.message);
175-
} else {
176-
console.warn(err.message);
177-
}
178-
},
179-
});
180-
124+
this.handleSexagesimalCoordinates(position);
181125
} else {
126+
this.handlePlaceOrAddress(position);
127+
}
128+
}
182129

183-
// Retrieve the coordinates for the provided POI or address
184-
185-
this.locationService.findPlaceCoordinates(position).subscribe({
186-
next: (place) => {
187-
place.displayName = position; // Show the name stored in metadata (comment out to show name retrieved from Nominatim)
188-
this.place.next(place);
189-
},
190-
error: (err) => {
191-
this.invalidLocationErrorCode.next(err.message); // either LOCATION_NOT_FOUND or API_ERROR
192-
if (err.message === LocationErrorCodes.API_ERROR) {
193-
console.error(err.message);
194-
} else {
195-
console.warn(err.message);
196-
}
197-
},
130+
private handleDecimalCoordinates(position: string) {
131+
if (this.locationService.isValidCoordinateString(position)) {
132+
const coordinates = this.locationService.parseCoordinates(position);
133+
this.locationService.searchByCoordinates(coordinates).subscribe({
134+
next: displayName => this.place.next({ coordinates, displayName }),
135+
error: err => this.handleError(err, coordinates)
198136
});
137+
} else {
138+
this.invalidLocationErrorCode.next(LocationErrorCodes.INVALID_COORDINATES);
199139
}
140+
}
141+
142+
private handleSexagesimalCoordinates(position: string) {
143+
this.locationService.findPlaceAndDecimalCoordinates(position).subscribe({
144+
next: place => this.place.next(place),
145+
error: err => this.handleError(err, this.locationService.parseCoordinates(position))
146+
});
147+
}
148+
149+
private handlePlaceOrAddress(position: string) {
150+
this.locationService.findPlaceCoordinates(position).subscribe({
151+
next: place => {
152+
place.displayName = position;
153+
this.place.next(place);
154+
},
155+
error: err => this.handleError(err, this.locationService.parseCoordinates(position))
156+
});
157+
}
200158

159+
private handleError(err: any, coordinates: LocationDDCoordinates) {
160+
this.invalidLocationErrorCode.next(err.message);
161+
this.place.next({ coordinates });
162+
console.error(err.message);
201163
}
202164

203165
private setCenterAndPointer(coordinates: LocationDDCoordinates) {
204166
this.leafletCenter = latLng(+coordinates.latitude, +coordinates.longitude);
205167
this.leafletLayers = [
206-
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 18, attribution: 'Leaflet'}),
207-
marker(
208-
[+coordinates.latitude, +coordinates.longitude],
209-
{icon: icon({iconUrl: 'assets/images/marker-icon.png', shadowUrl: 'assets/images/marker-shadow.png'})})
168+
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: 'Leaflet' }),
169+
marker([+coordinates.latitude, +coordinates.longitude], {
170+
icon: icon({ iconUrl: 'assets/images/marker-icon.png', shadowUrl: 'assets/images/marker-shadow.png' })
171+
})
210172
];
211173
}
212174
}

0 commit comments

Comments
 (0)