Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(directions): added possibility to toggle between two routing sources #1644

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ export interface DirectionsSourceOptions extends BaseDirectionsSourceOptions {
export type OsrmDirectionsSourceOptions = BaseDirectionsSourceOptions;

interface BaseDirectionsSourceOptions {
distance?: number;
name?: string;
baseUrl?: string;
profiles?: BaseDirectionsSourceOptionsProfile[];
}

export interface BaseDirectionsSourceOptionsProfile {
enabled?: boolean;
limit?: number;
logo?: string;
reverseUrl?: string;
type?: string;
url?: string;
name: string;
authorization?: BaseDirectionsSourceOptionsProfileAuthorization;
}

export interface BaseDirectionsSourceOptionsProfileAuthorization {
url: string;
property: string;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Position } from 'geojson';
import { Observable } from 'rxjs';

import { Direction, DirectionOptions } from '../shared/directions.interface';
import { BaseDirectionsSourceOptionsProfile } from './directions-source.interface';

export abstract class DirectionsSource {
abstract enabled: boolean;
abstract getName(): string;
abstract profiles: BaseDirectionsSourceOptionsProfile[];
abstract getSourceName(): string;
abstract getEnabledProfile(): BaseDirectionsSourceOptionsProfile;
abstract getProfileWithAuthorization(): BaseDirectionsSourceOptionsProfile;
abstract route(
coordinates: [number, number][],
coordinates: Position[],
directionsOptions: DirectionOptions
): Observable<Direction[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,117 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { ConfigService } from '@igo2/core/config';
import { customCacheHasher, uuid } from '@igo2/utils';
import { uuid } from '@igo2/utils';

import { Position } from 'geojson';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Cacheable } from 'ts-cacheable';

import {
DirectionsFormat,
SourceDirectionsType
} from '../shared/directions.enum';
import { Direction, DirectionOptions } from '../shared/directions.interface';
import { DirectionsSource } from './directions-source';
import { OsrmDirectionsSourceOptions } from './directions-source.interface';
import {
BaseDirectionsSourceOptionsProfile,
OsrmDirectionsSourceOptions
} from './directions-source.interface';

@Injectable()
export class OsrmDirectionsSource extends DirectionsSource {
get enabled(): boolean {
return this.options.enabled !== false;
get options(): OsrmDirectionsSourceOptions {
return this._options;
}

set options(value: OsrmDirectionsSourceOptions) {
this._options = value;
}

get sourceName(): string {
return this._options.name;
}

set sourceName(value: string) {
this._options.name = value;
}

get baseUrl(): string {
return this._options.baseUrl;
}

set baseUrl(value: string) {
this._options.baseUrl = value;
}

get url(): string {
return `${this.baseUrl}${this.getEnabledProfile().name}/`;
}

get profiles(): BaseDirectionsSourceOptionsProfile[] {
return this._options.profiles;
}
set enabled(value: boolean) {
this.options.enabled = value;

set profiles(value: BaseDirectionsSourceOptionsProfile[]) {
this._options.profiles = value;
}
static _name = 'OSRM Québec';
private directionsUrl =
'https://geoegl.msp.gouv.qc.ca/services/itineraire/route/v1/driving/';
private options: OsrmDirectionsSourceOptions;

private _options: OsrmDirectionsSourceOptions;

constructor(
private http: HttpClient,
private config: ConfigService
private _http: HttpClient,
private _config: ConfigService
) {
super();
this.options = this.config.getConfig('directionsSources.osrm') || {};
this.directionsUrl = this.options.url || this.directionsUrl;
this._options = this._config.getConfig('directionsSources.osrm');
if (!this.baseUrl) {
this.baseUrl = '/apis/itineraire/route/v1/';
}

if (!this.sourceName) {
this.sourceName = 'OSRM Québec';
}

if (!this.profiles) {
const profile: BaseDirectionsSourceOptionsProfile = {
enabled: true,
name: 'driving'
};
this.profiles = [profile];
} else {
if (!this.profiles.find((profile) => profile.enabled)) {
this.profiles[0].enabled = true;
}
}
}

getSourceName(): string {
return this.sourceName;
}

getEnabledProfile(): BaseDirectionsSourceOptionsProfile {
return this.profiles.find((profile) => profile.enabled);
}

getName(): string {
return OsrmDirectionsSource._name;
getProfileWithAuthorization(): BaseDirectionsSourceOptionsProfile {
return this.profiles.find((profile) => profile.authorization);
}

route(
coordinates: [number, number][],
coordinates: Position[],
directionsOptions: DirectionOptions = {}
): Observable<Direction[]> {
const directionsParams = this.getRouteParams(directionsOptions);
return this.getRoute(coordinates, directionsParams);
}

@Cacheable({
maxCacheCount: 20,
cacheHasher: customCacheHasher
})
private getRoute(
coordinates: [number, number][],
coordinates: Position[],
params: HttpParams
): Observable<Direction[]> {
return this.http
.get<JSON[]>(this.directionsUrl + coordinates.join(';'), {
const url: string = this.url;
return this._http
.get<JSON[]>(url + coordinates.join(';'), {
params
})
.pipe(map((res) => this.extractRoutesData(res)));
Expand Down Expand Up @@ -118,7 +171,7 @@ export class OsrmDirectionsSource extends DirectionsSource {
return {
id: uuid(),
title: roadNetworkRoute.legs[0].summary,
source: OsrmDirectionsSource._name,
source: this.url,
sourceType: SourceDirectionsType.Route,
order: 1,
format: DirectionsFormat.GeoJSON,
Expand Down
8 changes: 8 additions & 0 deletions packages/geo/src/lib/directions/directions.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
>
{{ 'igo.geo.directionsForm.toggleActive' | translate }}
</mat-slide-toggle>
<mat-slide-toggle
*ngIf="hasOsrmPrivateAccess && twoSourcesAvailable"
[checked]="enabledProfileHasAuthorization"
[labelPosition]="'before'"
(change)="onTogglePrivateModeControl($event.checked)"
>
{{ 'igo.geo.directionsForm.toggleType' | translate }}
</mat-slide-toggle>
</div>

<igo-directions-buttons
Expand Down
93 changes: 77 additions & 16 deletions packages/geo/src/lib/directions/directions.component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import {
ChangeDetectorRef,
Component,
Expand All @@ -10,6 +12,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { EntityStoreWatcher } from '@igo2/common';
import { LanguageService } from '@igo2/core/language';
import { IgoLanguageModule } from '@igo2/core/language';
import { MessageService } from '@igo2/core/message';
import { ChangeUtils, ObjectUtils } from '@igo2/utils';

import Collection from 'ol/Collection';
Expand All @@ -19,7 +22,7 @@ import { SelectEvent } from 'ol/interaction/Select';
import { TranslateEvent } from 'ol/interaction/Translate';
import * as olProj from 'ol/proj';

import { Subject, Subscription } from 'rxjs';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';

import { Feature } from '../feature/shared/feature.interfaces';
Expand All @@ -31,6 +34,8 @@ import { SearchService } from '../search/shared/search.service';
import { DirectionsButtonsComponent } from './directions-buttons/directions-buttons.component';
import { DirectionsInputsComponent } from './directions-inputs/directions-inputs.component';
import { DirectionsResultsComponent } from './directions-results/directions-results.component';
import { BaseDirectionsSourceOptionsProfile } from './directions-sources';
import { DirectionsSourceService } from './shared/directions-source.service';
import { DirectionType, ProposalType } from './shared/directions.enum';
import {
DirectionOptions,
Expand Down Expand Up @@ -60,6 +65,7 @@ import {
styleUrls: ['./directions.component.scss'],
standalone: true,
imports: [
CommonModule,
MatSlideToggleModule,
DirectionsButtonsComponent,
DirectionsInputsComponent,
Expand All @@ -71,6 +77,8 @@ export class DirectionsComponent implements OnInit, OnDestroy {
private watcher: EntityStoreWatcher<Stop>;

public projection: string = 'EPSG:4326';
public hasOsrmPrivateAccess: boolean = false;
public twoSourcesAvailable: boolean = false;

private zoomRoute$$: Subscription;
private storeEmpty$$: Subscription;
Expand All @@ -86,6 +94,7 @@ export class DirectionsComponent implements OnInit, OnDestroy {
public previousStops: Stop[] = [];

private searchs$$: Subscription[] = [];
private authenticated$$: Subscription;

@Input() contextUri: string;
@Input() stopsStore: StopsStore;
Expand All @@ -96,6 +105,7 @@ export class DirectionsComponent implements OnInit, OnDestroy {
@Input() length: number = 2;
@Input() coordRoundedDecimals: number = 6;
@Input() zoomToActiveRoute$: Subject<void> = new Subject();
@Input() authenticated$: BehaviorSubject<boolean>;

/**
* Wheter one of the direction control is active
Expand All @@ -109,15 +119,43 @@ export class DirectionsComponent implements OnInit, OnDestroy {
return [this.selectStopInteraction, this.translateStop, this.selectedRoute];
}

get enabledProfileHasAuthorization() {
return this.directionsSourceService.sources[0].getEnabledProfile()
.authorization;
}

constructor(
private cdRef: ChangeDetectorRef,
private http: HttpClient,
private languageService: LanguageService,
private directionsService: DirectionsService,
private directionsSourceService: DirectionsSourceService,
private searchService: SearchService,
private queryService: QueryService
private queryService: QueryService,
private messageService: MessageService
) {}

ngOnInit(): void {
this.authenticated$$ = this.authenticated$.subscribe(
(authenticated: boolean) => {
if (authenticated) {
const profileWithAuth: BaseDirectionsSourceOptionsProfile =
this.directionsSourceService.sources[0].getProfileWithAuthorization();
if (profileWithAuth) {
this.http
.get(profileWithAuth.authorization.url)
.subscribe((user) => {
this.hasOsrmPrivateAccess =
user[profileWithAuth.authorization.property];
});
}
}
}
);
this.twoSourcesAvailable =
this.directionsSourceService.sources[0].profiles.length === 2
? true
: false;
this.queryService.queryEnabled = false;
this.initEntityStores();
setTimeout(() => {
Expand All @@ -134,6 +172,7 @@ export class DirectionsComponent implements OnInit, OnDestroy {
this.storeChange$$.unsubscribe();
this.routesQueries$$.map((u) => u.unsubscribe());
this.zoomRoute$$.unsubscribe();
this.authenticated$$.unsubscribe();
this.freezeStores();
}

Expand Down Expand Up @@ -429,20 +468,18 @@ export class DirectionsComponent implements OnInit, OnDestroy {
isOverview ? overviewDirectionsOptions : undefined
);
if (routeResponse) {
routeResponse.map((res) =>
this.routesQueries$$.push(
res.subscribe((directions) => {
this.routesFeatureStore.deleteMany(this.routesFeatureStore.all());
directions.map((direction) =>
addDirectionToRoutesFeatureStore(
this.routesFeatureStore,
direction,
this.projection,
direction === directions[0] ? true : false
)
);
})
)
this.routesQueries$$.push(
routeResponse.subscribe((directions) => {
this.routesFeatureStore.deleteMany(this.routesFeatureStore.all());
directions.map((direction) =>
addDirectionToRoutesFeatureStore(
this.routesFeatureStore,
direction,
this.projection,
direction === directions[0] ? true : false
)
);
})
);
}
}
Expand All @@ -466,4 +503,28 @@ export class DirectionsComponent implements OnInit, OnDestroy {
: ol.removeInteraction(interaction)
);
}

onTogglePrivateModeControl(isActive: boolean) {
this.directionsSourceService.sources[0].profiles.forEach(
(profile) => (profile.enabled = false)
);
if (isActive) {
this.directionsSourceService.sources[0].profiles.find(
(profile) => profile.authorization
).enabled = true;
this.messageService.alert(
this.languageService.translate.instant(
'igo.geo.directionsForm.forestRoadsWarning.text'
),
this.languageService.translate.instant(
'igo.geo.directionsForm.forestRoadsWarning.title'
)
);
} else {
this.directionsSourceService.sources[0].profiles.find(
(profile) => !profile.authorization
).enabled = true;
}
this.getRoutes();
}
}
Loading
Loading