Skip to content

Commit

Permalink
feat(reverse search): Adding XY search by location on search-bar (#155)
Browse files Browse the repository at this point in the history
* Adding XY search by location on search-bar

* Providing same values as Icherche for sourceType

* Dependencies for proj4

* Translation string

* translation v2

* remove @ and projection

* Update package.json

* Update en.json

* space

* Update fr.json

* remove env
  • Loading branch information
pelord authored and mbarbeau committed May 18, 2018
1 parent e22e006 commit 2cd2bfa
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/demo-app/assets/locale/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Location": "Location",
"Search for an address or a place": "Search for an address or a place",
"Search for an address or a place": "Search for an address, a place or by coordinates (X,Y)",
"Submit": "Submit"
}
}
4 changes: 2 additions & 2 deletions src/demo-app/assets/locale/fr.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Location": "Localisation",
"Search for an address or a place": "Rechercher une adresse, un lieu ou une couche",
"Search for an address or a place": "Rechercher une adresse, un lieu, une couche ou par coordonnée (X,Y)",
"Submit": "Soumettre"
}
}
62 changes: 44 additions & 18 deletions src/lib/core/request/error.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent,
HttpErrorResponse } from '@angular/common/http';
import {
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpEvent,
HttpErrorResponse
} from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import { catchError } from 'rxjs/operators/catchError';
Expand All @@ -12,39 +17,59 @@ import { LanguageService } from '../language/shared/language.service';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

private httpError: HttpErrorResponse;

constructor(private messageService: MessageService,
private injector: Injector) {}
constructor(
private messageService: MessageService,
private injector: Injector
) {}

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error) => this.handleError(error, req)),
finalize(() => this.handleUncaughtError())
);
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next
.handle(req)
.pipe(
catchError(error => this.handleError(error, req)),
finalize(() => this.handleCaughtError()),
finalize(() => this.handleUncaughtError())
);
}

private handleError(httpError: HttpErrorResponse, req: HttpRequest<any>) {
const msg = `${req.method} ${req.urlWithParams} ${httpError.status} (${httpError.statusText})`;
const msg = `${req.method} ${req.urlWithParams} ${httpError.status} (${
httpError.statusText
})`;

if (httpError instanceof HttpErrorResponse) {
const errorObj = httpError.error === 'object' ? httpError.error : {};
const errorObj =
typeof httpError.error === 'object' ? httpError.error : {};
errorObj.message = httpError.error.message || httpError.statusText;
errorObj.caught = false;
console.error(msg, '\n', errorObj.message, '\n\n', httpError);

this.httpError = new HttpErrorResponse({
error: errorObj,
headers: httpError.headers,
status: httpError.status,
statusText: httpError.statusText,
url: httpError.url
error: errorObj,
headers: httpError.headers,
status: httpError.status,
statusText: httpError.statusText,
url: httpError.url
});
}

return new ErrorObservable(this.httpError);
};
}

private handleCaughtError() {
if (this.httpError && this.httpError.error.toDisplay) {
this.httpError.error.caught = true;
this.messageService.error(
this.httpError.error.message,
this.httpError.error.title
);
}
}

private handleUncaughtError() {
if (this.httpError && !this.httpError.error.caught) {
Expand All @@ -53,5 +78,6 @@ export class ErrorInterceptor implements HttpInterceptor {
const title = translate.instant('igo.errors.uncaught.title');
this.messageService.error(message, title);
}
this.httpError = undefined;
}
}
3 changes: 2 additions & 1 deletion src/lib/feature/shared/feature.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export enum FeatureFormat {
export enum SourceFeatureType {
Query = <any> 'Query',
Search = <any> 'Search',
Click = <any> 'Click'
Click = <any> 'Click',
LocateXY = <any> 'LocateXY'
}
30 changes: 26 additions & 4 deletions src/lib/search/search-bar/search-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,33 @@ export class SearchBarComponent implements OnInit, OnDestroy {
if (term !== undefined || term !== '') {
this.featureService.clear()
this.search.emit(term);
const r = this.searchService.search(term)
if (r) {
r.map(res => res.subscribe(
(features) => (this.featureService.updateFeatures(features as Feature[], undefined))))
// tslint:disable-next-line:max-line-length
if (/^([-+]?)([\d]{1,15})(((\.)?(\d+)?(,)))(\s*)(([-+]?)([\d]{1,15})((\.)?(\d+)?(;[\d]{4,5})?))$/g.test(term)) {
let xy
if (/(;[\d]{4,5})$/g.test(term)) {
const xyTerm = term.split(';');
// TODO Reproject coordinates
xy = JSON.parse('[' + xyTerm[0] + ']');
} else {
if (term.endsWith('.')) {
term += '0';
}
xy = JSON.parse('[' + term + ']');
}
const r = this.searchService.locate(xy);
if (r) {
r.filter(res => res !== undefined)
.map(res => res.subscribe(
(features) => (this.featureService.updateFeatures(features as Feature[], undefined)))
)
}
} else {
const r = this.searchService.search(term);
if (r) {
r.map(res => res.subscribe(
(features) => (this.featureService.updateFeatures(features as Feature[], undefined))))
}
}
}
}
}
6 changes: 3 additions & 3 deletions src/lib/search/search-sources/datasource-search-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

import { ConfigService, Message, LanguageService } from '../../core';
import { ConfigService, LanguageService } from '../../core';
import { Feature, FeatureType, SourceFeatureType } from '../../feature';

import { SearchSource } from './search-source';
Expand Down Expand Up @@ -33,15 +33,15 @@ export class DataSourceSearchSource extends SearchSource {
return this.languageService.translate.instant(DataSourceSearchSource._name);
}

search(term?: string): Observable<Feature[] | Message[]> {
search(term?: string): Observable<Feature[]> {
const searchParams = this.getSearchParams(term);

return this.http
.get(this.searchUrl, { params: searchParams })
.map(res => this.extractData(res));
}

locate(coordinate?: [number, number]): Observable<Feature[] | Message[]> {
locate(coordinate?: [number, number]): Observable<Feature[]> {
// It should be a good idea to locate layers by coordinates ?
return
}
Expand Down
75 changes: 64 additions & 11 deletions src/lib/search/search-sources/icherche-search-source.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import { catchError } from 'rxjs/operators/catchError';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';

import { ConfigService, Message } from '../../core';
import { ConfigService } from '../../core';
import {
Feature,
FeatureType,
Expand Down Expand Up @@ -40,26 +43,45 @@ export class IChercheSearchSource extends SearchSource {
return IChercheSearchSource._name;
}

search(term?: string): Observable<Feature[] | Message[]> {
search(term?: string): Observable<Feature[]> {
const searchParams = this.getSearchParams(term);

return this.http
.get(this.searchUrl, { params: searchParams })
.map(res => this.extractData(res));
.map(res => this.extractSearchData(res));
}

locate(
coordinate: [number, number],
zoom: number
): Observable<Feature[] | Message[]> {
): Observable<Feature[]> {
const locateParams = this.getLocateParams(coordinate, zoom);
return this.http
.get(this.locateUrl, { params: locateParams })
.map(res => this.extractData(res));
if (
coordinate[0] > -81 &&
coordinate[0] < -55 &&
coordinate[1] >= 43.1 &&
coordinate[1] < 64
) {
return this.http
.get(this.locateUrl, { params: locateParams })
.map(res => this.extractLocateData(res))
.pipe(
catchError(error => {
error.error.toDisplay = true;
error.error.title = this.getName();
error.error.message = error.error.message_erreur;
return new ErrorObservable(error);
})
);
}
}

private extractData(response): Feature[] {
return response.features.map(this.formatResult);
private extractSearchData(response): Feature[] {
return response.features.map(this.formatSearchResult);
}

private extractLocateData(response): Feature[] {
return response.features.map(this.formatLocateResult);
}

private getSearchParams(term: string): HttpParams {
Expand All @@ -83,12 +105,13 @@ export class IChercheSearchSource extends SearchSource {
currentZoom: number
): HttpParams {
let distance = 100;
const type = this.options.type || 'adresse';
const type = this.options.type || 'adresse,municipalite,mrc,regadmin';
if (currentZoom >= 16) {
distance = 30;
} else if (currentZoom < 8) {
distance = 500;
}

return new HttpParams({
fromObject: {
loc: coordinate.join(','),
Expand All @@ -99,7 +122,7 @@ export class IChercheSearchSource extends SearchSource {
});
}

private formatResult(result: any): Feature {
private formatSearchResult(result: any): Feature {
const properties = Object.assign(
{
type: result.doc_type
Expand Down Expand Up @@ -128,4 +151,34 @@ export class IChercheSearchSource extends SearchSource {
extent: result.bbox
};
}

private formatLocateResult(result: any): Feature {
const properties = Object.assign(
{
type: result.properties.doc_type
},
result.properties
);
delete properties.doc_type;
return {
id: result._id,
source: IChercheSearchSource._name,
sourceType: SourceFeatureType.LocateXY,
order: 1,
type: FeatureType.Feature,
format: FeatureFormat.GeoJSON,
title: result.properties.nom,
title_html: result.properties.nom,
icon: 'place',
projection: 'EPSG:4326',
properties: properties,
geometry: result.geometry,
extent: [
parseFloat(result.bbox[0]),
parseFloat(result.bbox[2]),
parseFloat(result.bbox[1]),
parseFloat(result.bbox[3])
]
};
}
}
23 changes: 13 additions & 10 deletions src/lib/search/search-sources/nominatim-search-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

import { ConfigService, Message } from '../../core';
import { ConfigService } from '../../core';
import {
Feature,
FeatureType,
Expand Down Expand Up @@ -41,26 +41,29 @@ export class NominatimSearchSource extends SearchSource {
return NominatimSearchSource._name;
}

search(term?: string): Observable<Feature[] | Message[]> {
search(term?: string): Observable<Feature[]> {
const searchParams = this.getSearchParams(term);

return this.http
.get(this.searchUrl, { params: searchParams })
.map(res => this.extractData(res));
.map(res => this.extractData(res, SourceFeatureType.Search));
}

locate(
coordinate: [number, number],
zoom: number
): Observable<Feature[] | Message[]> {
): Observable<Feature[]> {
const locateParams = this.getLocateParams(coordinate, zoom);
return this.http
.get(this.locateUrl, { params: locateParams })
.map(res => this.extractData([res]));
.map(res => this.extractData([res], SourceFeatureType.LocateXY));
}

private extractData(response): Feature[] {
return response.map(this.formatResult);
private extractData(response, resultType): Feature[] {
if (response[0] && response[0].error) {
return [];
}
return response.map(this.formatResult, resultType);
}

private getSearchParams(term: string): HttpParams {
Expand All @@ -84,17 +87,17 @@ export class NominatimSearchSource extends SearchSource {
lat: String(coordinate[1]),
lon: String(coordinate[0]),
format: 'json',
zoom: String(18),
zoom: String(zoom),
polygon_geojson: String(1)
}
});
}

private formatResult(result: any): Feature {
private formatResult(result: any, resultType): Feature {
return {
id: result.place_id,
source: NominatimSearchSource._name,
sourceType: SourceFeatureType.Search,
sourceType: resultType,
order: 0,
type: FeatureType.Feature,
format: FeatureFormat.GeoJSON,
Expand Down
5 changes: 2 additions & 3 deletions src/lib/search/search-sources/search-source.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Observable } from 'rxjs/Observable';

import { Message } from '../../core/message';
import { Feature } from '../../feature';


Expand All @@ -11,8 +10,8 @@ export abstract class SearchSource {

abstract getName(): string;

abstract search(term?: string): Observable<Feature[] | Message[]>
abstract search(term?: string): Observable<Feature[]>

abstract locate(coordinate: [number, number], zoom?: number): Observable<Feature[] | Message[]>
abstract locate(coordinate: [number, number], zoom?: number): Observable<Feature[]>

}

0 comments on commit 2cd2bfa

Please sign in to comment.