Skip to content

Commit

Permalink
feat(search): allow multiple search term separated by a term splitter (
Browse files Browse the repository at this point in the history
…#821)

* feat(search): allow multiple search term separated by a term splitter

* refactor(search-bar): validate if the term splitter exists in the term.

* feat(search): introducing the score property into the search result metadata based on server score OR on client side score calculation

* feat(demo) search with a term splitter

* refactor(search): lint

* refactor(search-results) moving from entities to stateview entities

* feat(search.state): adding a custom strategy based on score

* refactor(search source): moving from difference to similarity concept for client side score calculation

* refactor(search): lint

* wip

Co-authored-by: Marc-André Barbeau <[email protected]>
  • Loading branch information
pelord and mbarbeau authored Apr 8, 2021
1 parent c9362d2 commit 15db5e6
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 93 deletions.
1 change: 1 addition & 0 deletions demo/src/app/geo/search/search.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
(pointerSummaryStatus)="onPointerSummaryStatusChange($event)"
[searchSettings]="true"
[store]="searchStore"
[termSplitter]="termSplitter"
(searchTermChange)="onSearchTermChange($event)"
(search)="onSearch($event)"
(clearFeature)="removeFeatureFromMap()"
Expand Down
20 changes: 11 additions & 9 deletions demo/src/app/geo/search/search.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, forkJoin } from 'rxjs';
import {
Component,
ElementRef,
Expand Down Expand Up @@ -37,6 +37,8 @@ export class AppSearchComponent implements OnInit, OnDestroy {

public igoSearchPointerSummaryEnabled: boolean = false;

public termSplitter = '|';

public map = new IgoMap({
overlay: true,
controls: {
Expand Down Expand Up @@ -211,15 +213,15 @@ export class AppSearchComponent implements OnInit, OnDestroy {

onSearchCoordinate() {
this.searchStore.clear();
const results = this.searchService.reverseSearch(this.lonlat);
const researches = this.searchService.reverseSearch(this.lonlat);

for (const i in results) {
if (results.length > 0) {
results[i].request.subscribe((_results: SearchResult<Feature>[]) => {
this.onSearch({ research: results[i], results: _results });
});
}
}
researches.map((r: Research) => r.source).map((source) => {
const currentResearch = researches.find((r) => r.source === source);
return forkJoin(currentResearch.requests).subscribe((res: SearchResult[][]) => {
const results = [].concat.apply([], res);
this.onSearch({ research: currentResearch, results });
});
});
}

onOpenGoogleMaps() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import {
ChangeDetectorRef
} from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormArray } from '@angular/forms';
import { Subscription, Subject, BehaviorSubject } from 'rxjs';
import { Subscription, Subject, BehaviorSubject, forkJoin } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
map
distinctUntilChanged
} from 'rxjs/operators';

import olFeature from 'ol/Feature';
Expand Down Expand Up @@ -436,15 +435,16 @@ export class DirectionsFormComponent implements OnInit, OnDestroy {
.reverseSearch(coordinates, { zoom: this.map.viewController.getZoom() })
.map(res =>
this.routesQueries$$.push(
res.request.pipe(map(f => f)).subscribe(results => {
forkJoin(res.requests).subscribe(r => {
const results = [].concat.apply([], r);
results.forEach(result => {
if (
groupedLocations.filter(f => f.source === result.source)
.length === 0
) {
groupedLocations.push({
source: result.source,
results: results.map(r => r.data)
results: results.map(response => response.data)
});
}
});
Expand Down Expand Up @@ -1156,18 +1156,19 @@ export class DirectionsFormComponent implements OnInit, OnDestroy {
const researches = this.searchService.search(term, {searchType: 'Feature'});
researches.map(res =>
this.search$$ =
res.request.subscribe(results => {
forkJoin(res.requests).subscribe(r => {
const results = [].concat.apply([], r);
results
.filter(r => r.data.geometry)
.filter(resp => resp.data.geometry)
.forEach(element => {
if (
searchProposals.filter(r => r.source === element.source)
searchProposals.filter(q => q.source === element.source)
.length === 0
) {
searchProposals.push({
source: element.source,
meta: element.meta,
results: results.map(r => r.data)
results: results.map(p => p.data)
});
}
});
Expand Down
20 changes: 15 additions & 5 deletions packages/geo/src/lib/search/search-bar/search-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
FloatLabelType,
MatFormFieldAppearance
} from '@angular/material/form-field';
import { BehaviorSubject, Subscription, EMPTY, timer } from 'rxjs';
import { BehaviorSubject, Subscription, EMPTY, timer, forkJoin } from 'rxjs';
import { debounce, distinctUntilChanged } from 'rxjs/operators';

import { LanguageService } from '@igo2/core';
Expand Down Expand Up @@ -113,6 +113,8 @@ export class SearchBarComponent implements OnInit, OnDestroy {
}
readonly term$: BehaviorSubject<string> = new BehaviorSubject('');

@Input() termSplitter;

/**
* Whether this component is disabled
*/
Expand Down Expand Up @@ -404,12 +406,20 @@ export class SearchBarComponent implements OnInit, OnDestroy {
return;
}

const researches = this.searchService.search(term, {
const terms = this.termSplitter && this.term.match(new RegExp(this.termSplitter, 'g')) ?
term.split(this.termSplitter).filter((t) => t.length >= this.minLength ) : [term];

if (!terms.length) {
return;
}
const researches = this.searchService.search(terms, {
forceNA: this.forceNA
});
this.researches$$ = researches.map((research) => {
return research.request.subscribe((results: SearchResult[]) => {
this.onResearchCompleted(research, results);
this.researches$$ = researches.map((r: Research) => r.source).map((source) => {
const currentResearch = researches.find((r) => r.source === source);
return forkJoin(currentResearch.requests).subscribe((res: SearchResult[][]) => {
const results = [].concat.apply([], res);
this.onResearchCompleted(currentResearch, results);
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
} from '@angular/core';
import type { TemplateRef } from '@angular/core';

import { Observable, EMPTY, timer, BehaviorSubject, Subscription } from 'rxjs';
import { Observable, EMPTY, timer, BehaviorSubject, Subscription, forkJoin } from 'rxjs';
import { debounce, map } from 'rxjs/operators';

import { EntityStore, EntityStoreWatcher } from '@igo2/common';
import { EntityState, EntityStore, EntityStoreWatcher } from '@igo2/common';

import { IgoMap } from '../../map';

Expand Down Expand Up @@ -91,6 +91,8 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
}
public _term: string;

@Input() termSplitter;

@Input() settingsChange$ = new BehaviorSubject<boolean>(undefined);

/**
Expand Down Expand Up @@ -195,12 +197,12 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
* @internal
*/
private liftResults(): Observable<{source: SearchSource; results: SearchResult[]}[]> {
return this.store.view.all$().pipe(
debounce((results: SearchResult[]) => {
return this.store.stateView.all$().pipe(
debounce((results: {entity: SearchResult, state: EntityState}[]) => {
return results.length === 0 ? EMPTY : timer(200);
}),
map((results: SearchResult[]) => {
return this.groupResults(results.sort(this.sortByOrder));
map((results: {entity: SearchResult, state: EntityState}[]) => {
return this.groupResults(results.map(r => r.entity).sort(this.sortByOrder));
})
);
}
Expand Down Expand Up @@ -249,14 +251,18 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
page: ++this.pageIterator[group.source.getId()]
};

const researches = this.searchService.search(this.term, options);
researches.map(research => {
research.request.subscribe((results: SearchResult[]) => {
const terms = this.termSplitter ? this.term.split(this.termSplitter) : [this.term];

const researches = this.searchService.search(terms, options);
researches.map((r: Research) => r.source).map((source) => {
const currentResearch = researches.find((r) => r.source === source);
return forkJoin(currentResearch.requests).subscribe((res: SearchResult[][]) => {
const results = [].concat.apply([], res);
const newResults = group.results.concat(results);
if (!results.length) {
newResults[newResults.length - 1].meta.nextPage = false;
}
this.moreResults.emit({research, results: newResults});
this.moreResults.emit({research: currentResearch, results: newResults});
});
});
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
AfterContentChecked
} from '@angular/core';

import { Subscription } from 'rxjs';
import { forkJoin, Subscription } from 'rxjs';

import { MapBrowserPointerEvent as OlMapBrowserPointerEvent } from 'ol/MapBrowserEvent';
import { ListenerFunction } from 'ol/events';
Expand Down Expand Up @@ -273,16 +273,15 @@ export class SearchPointerSummaryDirective implements OnInit, OnDestroy, AfterCo

private onSearchCoordinate() {
this.pointerSearchStore.clear();
const results = this.searchService.reverseSearch(this.lonLat, { params: { geometry: 'false', icon: 'false' } }, true);

for (const i in results) {
if (results.length > 0) {
this.reverseSearch$$.push(
results[i].request.subscribe((_results: SearchResult<Feature>[]) => {
this.onSearch({ research: results[i], results: _results });
}));
}
}
const researches = this.searchService.reverseSearch(this.lonLat, { params: { geometry: 'false', icon: 'false' } }, true);

researches.map((r: Research) => r.source).map((source) => {
const currentResearch = researches.find((r) => r.source === source);
return forkJoin(currentResearch.requests).subscribe((res: SearchResult[][]) => {
const results = [].concat.apply([], res);
this.onSearch({ research: currentResearch, results });
});
});
}

private onSearch(event: { research: Research; results: SearchResult[] }) {
Expand Down
3 changes: 2 additions & 1 deletion packages/geo/src/lib/search/shared/search.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
import { SearchSource } from './sources/source';

export interface Research {
request: Observable<SearchResult[]>;
requests: Observable<SearchResult[]>[];
reverse: boolean;
source: SearchSource;
}
Expand All @@ -17,6 +17,7 @@ export interface SearchResult<T = { [key: string]: any }> {
title: string;
titleHtml?: string;
icon: string;
score?: number;
nextPage?: boolean;
};
}
57 changes: 38 additions & 19 deletions packages/geo/src/lib/search/shared/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,32 @@ export class SearchService {

/**
* Perform a research by text
* @param term Any text
* @param terms Any text or a list of terms
* @returns Researches
*/
search(term: string, options: TextSearchOptions = {}): Research[] {
if (!this.termIsValid(term)) {
search(
terms: string[] | string, // for compatibility
options: TextSearchOptions = {}
): Research[] {
const validatedTerms = this.validateTerms(this.stringToArray(terms));

if (validatedTerms.length === 0) {
return [];
}

const proj = this.mapService.getMap()?.projection || 'EPSG:3857';
const response = stringToLonLat(term, proj, {
forceNA: options.forceNA
validatedTerms.map(term => {
const response = stringToLonLat(term, proj, {
forceNA: options.forceNA
});
if (response.lonLat) {
return this.reverseSearch(response.lonLat, { distance: response.radius });
} else if (response.message) {
console.log(response.message);
}
});
if (response.lonLat) {
return this.reverseSearch(response.lonLat, { distance: response.radius, conf: response.conf });
} else if (response.message) {
console.log(response.message);
}

options.extent = this.mapService
.getMap()
?.viewController.getExtent('EPSG:4326');
options.extent = this.mapService.getMap()?.viewController.getExtent('EPSG:4326');

let sources;

Expand All @@ -73,7 +78,7 @@ export class SearchService {
}

sources = sources.filter(sourceCanSearch);
return this.searchSources(sources, term, options);
return this.searchSources(sources, validatedTerms, options);
}

/**
Expand Down Expand Up @@ -103,15 +108,16 @@ export class SearchService {
*/
private searchSources(
sources: SearchSource[],
term: string,
terms: string[] | string, // for compatibility
options: TextSearchOptions
): Research[] {

return sources.map((source: SearchSource) => {
return {
request: ((source as any) as TextSearch).search(term, options),
requests: this.stringToArray(terms).map((term) => ((source as any) as TextSearch).search(term, options)),
reverse: false,
source
};
} as Research;
});
}

Expand All @@ -128,16 +134,25 @@ export class SearchService {
): Research[] {
return sources.map((source: SearchSource) => {
return {
request: ((source as any) as ReverseSearch).reverseSearch(
requests: [((source as any) as ReverseSearch).reverseSearch(
lonLat,
options
),
)],
reverse: true,
source
};
});
}

/**
* Validate if a list of term is valid
* @param term Search term
* @returns The validated list;
*/
private validateTerms(terms: string[]): string[] {
return terms.filter((term) => this.termIsValid(term));
}

/**
* Validate that a search term is valid
* @param term Search term
Expand All @@ -146,4 +161,8 @@ export class SearchService {
private termIsValid(term: string): boolean {
return typeof term === 'string' && term !== '';
}

private stringToArray(terms: string[] | string): string[] {
return typeof terms === 'string' ? [terms] : terms;
}
}
23 changes: 23 additions & 0 deletions packages/geo/src/lib/search/shared/search.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,26 @@ export function featureToSearchResult(
}
};
}

export function findDiff(str1: string, str2: string){
let diff = '';
str2.split('').forEach((val, i) => {
if (val !== str1.charAt(i)) {
diff += val;
}
});
return diff;
}

export function computeTermSimilarity(from, to): number {
const fromToDiff = findDiff(from, to);
const toFromDiff = findDiff(to, from);
const totalDiff = fromToDiff + toFromDiff;

let delta = 0;
if (totalDiff.length) {
delta = totalDiff.length / from.length * 100;
}

return 100 - Math.floor(delta);
}
Loading

0 comments on commit 15db5e6

Please sign in to comment.