diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1a809151..9ca0d3b5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -67,7 +67,8 @@ import { OsmRoutePoiService, GooglePoiService, ReverseGeocodingService, - LanguageService + LanguageService, + HikeProgramService } from './shared/services'; // Vendor import { AngularFireModule } from 'angularfire2'; @@ -177,6 +178,7 @@ export class CustomRouterStateSerializer implements RouterStateSerializer { // Refresh markers diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-hike/index.ts b/src/app/pages/hike-edit/components/hike-edit-pois-hike/index.ts index e11da565..e15560f6 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-hike/index.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-hike/index.ts @@ -2,25 +2,19 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; -import { - PoiSelectors, CenterRadius, GeometryService, GeoSearchSelectors, Poi, - PoiSaved, IGeoSearchContextState, RouteSelectors, ElevationService, ISegment, GameRuleService -} from 'subrepos/gtrack-common-ngx'; +import { PoiSelectors, GeoSearchSelectors, Poi } from 'subrepos/gtrack-common-ngx'; import { GeospatialService } from 'subrepos/gtrack-common-ngx/app/shared/services/geospatial'; import { IPoiStored, IPoi, IHikeProgramStop } from 'subrepos/provider-client'; import { AdminMap, AdminMapService, AdminMapMarker } from 'app/shared/services/admin-map'; -import { PoiEditorService } from 'app/shared/services'; +import { PoiEditorService, HikeProgramService } from 'app/shared/services'; import { IGTrackPoi } from 'app/shared/interfaces'; -import { - State, hikeEditPoiActions, IExternalPoiListContextState, commonPoiActions, commonGeoSearchActions, IHikeEditRoutePlannerState, editedHikeProgramActions -} from 'app/store'; +import { State, hikeEditPoiActions, commonPoiActions } from 'app/store'; import { HikeEditPoiSelectors, HikeEditMapSelectors, HikeEditRoutePlannerSelectors, EditedHikeProgramSelectors } from 'app/store/selectors'; import * as _ from 'lodash'; import * as uuid from 'uuid/v1'; -import * as turf from '@turf/turf'; @Component({ selector: 'gt-hike-edit-pois-hike', @@ -42,10 +36,7 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { private _hikeEditPoiSelectors: HikeEditPoiSelectors, private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, private _geoSearchSelectors: GeoSearchSelectors, - private _geometryService: GeometryService, - private _geospatialService: GeospatialService, - private _elevationService: ElevationService, - private _gameRuleService: GameRuleService, + private _hikeProgramService: HikeProgramService, private _poiSelectors: PoiSelectors ) {} @@ -64,7 +55,7 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { this._store.select(this._editedHikeProgramSelectors.getPoiIds).takeUntil(this._destroy$), this._store.select(this._poiSelectors.getPoiIds).takeUntil(this._destroy$) ) - .debounceTime(150) + .debounceTime(200) .takeUntil(this._destroy$) .subscribe(([inHikePoiIds, inStorePoiIds]: [string[], string[]]) => { const missingPoiIds = _.difference(inHikePoiIds, _.intersection(inHikePoiIds, inStorePoiIds)) @@ -78,6 +69,7 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { // Poi list this.pois$ = this._store .select(this._editedHikeProgramSelectors.getHikePois(this._poiSelectors.getAllPois)) + .debounceTime(200) .takeUntil(this._destroy$) .filter((pois: IPoiStored[]) => typeof pois !== 'undefined') .switchMap((pois: IPoiStored[]) => { @@ -91,7 +83,7 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { }); this.pois$ - .debounceTime(150) + .debounceTime(200) .takeUntil(this._destroy$) .subscribe((pois: IGTrackPoi[]) => { // Refresh markers @@ -102,14 +94,14 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { .select(this._editedHikeProgramSelectors.getStopsCount) .takeUntil(this._destroy$) .subscribe((stopsCount: number) => { - if (stopsCount > 0) { - this._store - .select(this._editedHikeProgramSelectors.getStops) - .take(1) - .subscribe((stops: IHikeProgramStop[]) => { - this._updateStopsSegment(_.orderBy(_.cloneDeep(stops), ['distanceFromOrigo'])); - }); - } + this._hikeProgramService.updateHikeProgramStops(); + }); + + this._store + .select(this._hikeEditRoutePlannerSelectors.getPathLength) + .takeUntil(this._destroy$) + .subscribe((pathLength: number) => { + this._hikeProgramService.updateHikeProgramStops(); }); // @@ -159,37 +151,4 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { public toggleOffrouteMarkers() { this._store.dispatch(new hikeEditPoiActions.ToggleOffrouteMarkers('hike')); } - - /** - * Update stops' segment info - */ - private _updateStopsSegment(stops: IHikeProgramStop[]) { - this._store - .select(this._hikeEditRoutePlannerSelectors.getPath) - .take(1) - .subscribe(path => { - if (path.geometry.coordinates.length > 0) { - let _segmentStartPoint = path.geometry.coordinates[0]; - - for (const stop of stops) { - const _segmentEndPoint = [stop.lon, stop.lat]; - const _segmentPath = this._geospatialService.snappedLineSlice(_segmentStartPoint, _segmentEndPoint, path); - const _segmentDistance = 1000 * turf.lineDistance(_segmentPath, {units: 'kilometers'}); - - stop.segment = { - uphill: this._elevationService.calculateUphill((_segmentPath).geometry.coordinates), - downhill: this._elevationService.calculateDownhill((_segmentPath).geometry.coordinates), - distance: _segmentDistance - } - stop.segment.time = this._gameRuleService.segmentTime(_segmentDistance, stop.segment.uphill), - stop.segment.score = this._gameRuleService.score(_segmentDistance, stop.segment.uphill) - - // Save coords for the next segment - _segmentStartPoint = [stop.lon, stop.lat]; - } - - this._store.dispatch(new editedHikeProgramActions.SetStops(stops)); - } - }); - } } diff --git a/src/app/shared/services/hike/hike-program.service.ts b/src/app/shared/services/hike/hike-program.service.ts new file mode 100644 index 00000000..a6fd6793 --- /dev/null +++ b/src/app/shared/services/hike/hike-program.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Store } from '@ngrx/store'; +import { State, editedHikeProgramActions } from 'app/store'; +import { EditedHikeProgramSelectors, HikeEditRoutePlannerSelectors } from 'app/store/selectors'; +import { IHikeProgramStop } from 'subrepos/provider-client'; +import { GeospatialService } from 'subrepos/gtrack-common-ngx/app/shared/services/geospatial'; +import { ElevationService, GameRuleService } from 'subrepos/gtrack-common-ngx'; + +import * as _ from 'lodash'; +import * as turf from '@turf/turf'; + +@Injectable() +export class HikeProgramService { + constructor( + private _store: Store, + private _geospatialService: GeospatialService, + private _elevationService: ElevationService, + private _gameRuleService: GameRuleService, + private _editedHikeProgramSelectors: EditedHikeProgramSelectors, + private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, + ) {} + + /** + * Update stop segments and start/end points + */ + public updateHikeProgramStops() { + Observable + .combineLatest( + this._store.select(this._editedHikeProgramSelectors.getStops).take(1), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath).take(1) + ) + .take(1) + .subscribe(([stops, path]: [IHikeProgramStop[], any]) => { + const poiStops = _.cloneDeep(stops).filter(stop => stop.poiId); + console.log('poiStops', poiStops); + if (path.geometry.coordinates.length > 0) { + poiStops.unshift(this._createStopFromPathEndPoint(path, 0)); + poiStops.push(this._createStopFromPathEndPoint(path, path.geometry.coordinates.length - 1)); + } + this._updateStopsSegment(_.orderBy(poiStops, ['distanceFromOrigo']), path); + }); + } + + /** + * Create begin/end stop from path endpoints + */ + private _createStopFromPathEndPoint(path, coordIdx) { + const coord = path.geometry.coordinates[coordIdx]; + return { + distanceFromOrigo: coord === 0 ? 0 : this._geospatialService.distanceOnLine( + path.geometry.coordinates[0], + coord, + path + ), + onRoute: true, + lat: coord[1], + lon: coord[0], + segment: { + uphill: 0, + downhill: 0, + distance: 0, + score: 0, + time: 0 + } + } + } + + /** + * Update stops' segment info + */ + private _updateStopsSegment(stops: IHikeProgramStop[], path: any) { + if (_.get(path, 'geometry.coordinates', []).length > 0) { + let _segmentStartPoint = path.geometry.coordinates[0]; + console.log('_segmentStartPoint', _segmentStartPoint); + for (const stop of stops) { + const _segmentEndPoint = [stop.lon, stop.lat]; + const _segmentPath = this._geospatialService.snappedLineSlice(_segmentStartPoint, _segmentEndPoint, path); + const _segmentDistance = 1000 * turf.lineDistance(_segmentPath, {units: 'kilometers'}); + + stop.segment = { + uphill: this._elevationService.calculateUphill((_segmentPath).geometry.coordinates), + downhill: this._elevationService.calculateDownhill((_segmentPath).geometry.coordinates), + distance: _segmentDistance + } + stop.segment.time = this._gameRuleService.segmentTime(_segmentDistance, stop.segment.uphill), + stop.segment.score = this._gameRuleService.score(_segmentDistance, stop.segment.uphill) + + // Save coords for the next segment + _segmentStartPoint = [stop.lon, stop.lat]; + } + + this._store.dispatch(new editedHikeProgramActions.SetStops(stops)); + } + } +} diff --git a/src/app/shared/services/hike-data/reverse-geocoding.service.ts b/src/app/shared/services/hike/reverse-geocoding.service.ts similarity index 100% rename from src/app/shared/services/hike-data/reverse-geocoding.service.ts rename to src/app/shared/services/hike/reverse-geocoding.service.ts diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index 8a0e69fa..b9ce0ee2 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -1,4 +1,5 @@ -export * from './hike-data/reverse-geocoding.service'; +export * from './hike/reverse-geocoding.service'; +export * from './hike/hike-program.service'; export * from './admin-map/admin-map.service'; export * from './poi'; export * from './language.service'; diff --git a/src/app/store/selectors/hike-edit-route-planner.ts b/src/app/store/selectors/hike-edit-route-planner.ts index 29ced4e9..55eb35c6 100644 --- a/src/app/store/selectors/hike-edit-route-planner.ts +++ b/src/app/store/selectors/hike-edit-route-planner.ts @@ -13,6 +13,7 @@ export class HikeEditRoutePlannerSelectors { public getRoutePlanner: MemoizedSelector; public getRoute: MemoizedSelector; public getPath: MemoizedSelector; + public getPathLength: MemoizedSelector; public getSegments: MemoizedSelector; public getTotal: MemoizedSelector; public getIsRoundTrip: MemoizedSelector; @@ -33,6 +34,10 @@ export class HikeEditRoutePlannerSelectors { (state: IHikeEditRoutePlannerState) => state.route.features[0] ); + this.getPathLength = createSelector(this._featureSelector, + (state: IHikeEditRoutePlannerState) => _.get(state.route.features[0], 'geometry.coordinates', []).length + ); + this.getSegments = createSelector(this._featureSelector, (state: IHikeEditRoutePlannerState) => state.segments ); diff --git a/src/subrepos/provider-client/interfaces/hike-program.ts b/src/subrepos/provider-client/interfaces/hike-program.ts index 8ccb6a34..2e2591a7 100644 --- a/src/subrepos/provider-client/interfaces/hike-program.ts +++ b/src/subrepos/provider-client/interfaces/hike-program.ts @@ -22,7 +22,7 @@ export interface IRouteSegment { export interface IHikeProgramStop { distanceFromOrigo: number; - poiId: string; + poiId?: string; lat: number; lon: number; onRoute?: boolean;