import { Injectable } from '@angular/core'; import { Store, Action,createFeatureSelector } from '@ngrx/store'; import { ROUTER_NAVIGATED, RouterReducerState } from '@ngrx/router-store'; import * as fromRouter from '@ngrx/router-store'; import { createEffect, Actions,ofType } from '@ngrx/effects'; import { EMPTY, Observable , of} from 'rxjs'; import { withLatestFrom, switchMap, map, catchError, mergeMap } from 'rxjs/operators'; import {GeoJSON,WKT} from 'ol/format'; import {Feature} from 'ol'; import { getCenter,createEmpty,extend } from 'ol/extent'; import {Point,Geometry} from 'ol/geom' import * as mapActions from '../actions/map.actions'; import * as mapReducers from '../reducers/map.reducer'; import {commonReducers} from '@farmmaps/common'; import {commonActions} from '@farmmaps/common'; import { IItem } from '@farmmaps/common'; import { FolderService, ItemService } from '@farmmaps/common'; import { tassign } from 'tassign'; import {FeatureIconService} from '../services/feature-icon.service'; import * as style from 'ol/style'; import { ItemTypeService,IQueryState } from '@farmmaps/common'; import { TemporalItemLayer } from '../models/item.layer' export const getRouterState = createFeatureSelector('router'); export const { selectCurrentRoute, // select the current route selectQueryParams, // select the current route query params selectQueryParam, // factory function to select a query param selectRouteParams, // select the current route params selectRouteParam, // factory function to select a route param selectRouteData, // select the current route data selectUrl, // select the current url } = fromRouter.getSelectors(getRouterState); @Injectable() export class MapEffects { private _geojsonFormat: GeoJSON; private _wktFormat: WKT; private overrideSelectedItemLayer: boolean = false; private updateFeatureGeometry(feature:Feature, updateEvent:commonActions.DeviceUpdateEvent): Feature { let newFeature = feature.clone(); var f = this._wktFormat.readFeature(updateEvent.attributes["geometry"],{ dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); var centroid = getCenter(f.getGeometry().getExtent()); newFeature.setId(feature.getId()); newFeature.setGeometry(new Point(centroid)); return newFeature; } init$ = createEffect(() => this.actions$.pipe( ofType(mapActions.INIT), withLatestFrom(this.store$.select(commonReducers.selectGetRootItems)), switchMap(([action, rootItems]) => { let actions=[]; for (let rootItem of rootItems) { if (rootItem.itemType == "UPLOADS_FOLDER") actions.push(new mapActions.SetParent(rootItem.code)); } // initialize default feature styles actions.push(new mapActions.SetStyle('file',new style.Style({ image: new style.Icon({ anchor: [0.5, 1], scale: 0.05, src: this.featureIconService$.getIconImageDataUrl("fal fa-file") }), stroke: new style.Stroke({ color: 'red', width: 1 }), fill: new style.Fill({ color: 'rgba(0, 0, 0,0)' }) }))); actions.push(new mapActions.SetStyle('selected',new style.Style({ image: new style.Icon({ anchor: [0.5, 1], scale: 0.08, src: this.featureIconService$.getIconImageDataUrl(null) }), stroke: new style.Stroke({ color: 'red', width: 3 }), fill: new style.Fill({ color: 'rgba(0, 0, 0, 0)' }) }))); return actions; } ))); initBaseLayers$ = createEffect(() => this.actions$.pipe( ofType(mapActions.INIT), withLatestFrom(this.store$.select(mapReducers.selectGetProjection)), map(([action, projection]) => new mapActions.LoadBaseLayers(projection))) ); loadBaseLayers$ = createEffect(() => this.actions$.pipe( ofType(mapActions.LOADBASELAYERS), switchMap((action: mapActions.LoadBaseLayers) => { return this.itemService$.getItemList("vnd.farmmaps.itemtype.layer", { "isBaseLayer": true }).pipe( map((items: IItem[]) => new mapActions.LoadBaseLayersSuccess(items)), catchError(error => of(new commonActions.Fail(error)))); }))); startSearch$ = createEffect(() => this.actions$.pipe( ofType(mapActions.STARTSEARCH), switchMap((action) => { let a = action as mapActions.StartSearch; var startDate = a.queryState.startDate; var endDate = a.queryState.endDate; var newAction:Observable; if (a.queryState.itemCode || a.queryState.parentCode || a.queryState.itemType || a.queryState.query || a.queryState.tags) { newAction= this.itemService$.getFeatures(a.queryState.bbox, "EPSG:3857", a.queryState.query, a.queryState.tags, startDate, endDate, a.queryState.itemType, a.queryState.parentCode, a.queryState.dataFilter, a.queryState.level).pipe( switchMap((features: any) => { for (let f of features.features) { if (f.properties && f.properties["code"]) { f.id = f.properties["code"]; } } return of(new mapActions.StartSearchSuccess(this._geojsonFormat.readFeatures(features), a.queryState)); } ), catchError(error => of(new commonActions.Fail(error)))); } else { return []; } return newAction; }))); zoomToExtent$ = createEffect(() => this.actions$.pipe( ofType(mapActions.STARTSEARCHSUCCESS), mergeMap((action: mapActions.StartSearchSuccess) => { let actions =[]; actions.push(new commonActions.SetMenuVisible(false)); let extent = createEmpty(); if (!action.query.bboxFilter) { if (extent) { for (let f of action.features) { extend(extent, (f as Feature).getGeometry().getExtent()); } if(action.features && action.features.length >0) { actions.push(new mapActions.SetExtent(extent)); } } } return actions; }))); zoomToExtent2$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SETFEATURES), switchMap((action: mapActions.SetFeatures) => { let extent = createEmpty(); if (extent) { for (let f of action.features) { extend(extent, (f as Feature).getGeometry().getExtent()); } if(action.features.length>0) return of(new mapActions.SetExtent(extent)); } return EMPTY; }))); hideMenu$ = createEffect(() => this.actions$.pipe( ofType(mapActions.STARTSEARCHSUCCESS), mergeMap((action: mapActions.StartSearchSuccess) => { return of(new commonActions.SetMenuVisible(false)); }))); selectItem$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SELECTITEM), withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItem)), switchMap(([action, selectedItem]) => { let a = action as mapActions.SelectItem; let itemCode = selectedItem ? selectedItem.code : ""; if (a.itemCode != itemCode) { return this.itemService$.getItem(a.itemCode).pipe( switchMap(child => { return this.itemService$.getItem(child.parentCode) .pipe(map(parent => { return {child, parent}; }),catchError(() => { let parent:IItem = null;return of({child,parent})}) ); }), map(data => new mapActions.SelectItemSuccess(data.child, data.parent)), catchError(error => of(new commonActions.Fail(error)))) } else { return []; } } ))); selectItemSuccessSetLayer$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SELECTITEMSUCCESS), map((action:mapActions.SelectItemSuccess) => new mapActions.SetSelectedItemLayer(action.item) ) )); selectItemSuccess$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SELECTITEMSUCCESS), switchMap((action:mapActions.SelectItemSuccess) => { if(!this.overrideSelectedItemLayer) { return this.itemService$.getFeature(action.item.code, "EPSG:3857").pipe( map((feature: any) => { let f = this._geojsonFormat.readFeature(feature); f.setId(action.item.code); return new mapActions.AddFeatureSuccess(f ); }), catchError(error => of(new commonActions.Fail(error)))); } else { return EMPTY; } } ))); selectItemSuccessTemporal$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SELECTITEMSUCCESS), switchMap((action:mapActions.SelectItemSuccess) => { if(action.item.itemType == "vnd.farmmaps.itemtype.temporal") { return this.itemService$.getChildItemList(action.item.code,null).pipe( map(items => new mapActions.SelectTemporalItemsSuccess( items.sort((a, b) => -(Date.parse(b.dataDate) - Date.parse(a.dataDate)) ) )), catchError(error => of(new commonActions.Fail(error)))); } else { return []; } } ))); uploadedItemClick$ = createEffect(() => this.actions$.pipe( ofType(commonActions.UPLOADEDFILECLICK), switchMap((action: commonActions.UploadedFileClick) => of(new mapActions.DoQuery(tassign(mapReducers.initialState.query.querystate, {itemCode:action.itemCode}))) ))); featureUpdate$ = createEffect(() => this.actions$.pipe( ofType(commonActions.DEVICEUPDATEEVENT), withLatestFrom(this.store$.select(mapReducers.selectGetFeatures)), mergeMap(([action, features]) => { let deviceUpdateEventAction = action as commonActions.DeviceUpdateEvent; var feature: Feature = null; for (let f of features) { if (f.getId() == deviceUpdateEventAction.itemCode) { feature = f; break; } } if (feature) { return of(new mapActions.UpdateFeatureSuccess(this.updateFeatureGeometry(feature,deviceUpdateEventAction))); } else { return []; } }))); itemUpdate$ = createEffect(() => this.actions$.pipe( ofType(commonActions.ITEMCHANGEDEVENT), withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItem)), mergeMap(([action, selectedItem]) => { let itemChangedAction = action as commonActions.ItemChangedEvent; if (selectedItem && selectedItem.code == itemChangedAction.itemCode) { return this.itemService$.getItem(itemChangedAction.itemCode).pipe( switchMap(child => { return this.itemService$.getItem(child.parentCode) .pipe(map(parent => { return {child, parent}; }) ); }), map(data => new mapActions.SelectItemSuccess(data.child, data.parent)), catchError(error => of(new commonActions.Fail(error)))); } else { return []; } }))); getActionFromQueryState(queryState:IQueryState, inSearch:boolean):Observable|[] { if(!inSearch && (queryState.itemType || queryState.parentCode || queryState.itemCode || queryState.query || queryState.tags)) { var newAction:Action; if (queryState.itemCode && queryState.itemCode != "") { newAction= new mapActions.SelectItem(queryState.itemCode); } else { newAction= new mapActions.StartSearch(queryState); } } else { newAction = new mapActions.Clear(); } return of(newAction); } getLayerValue$ = createEffect(() => this.actions$.pipe( ofType(mapActions.GETLAYERVALUE), mergeMap((action:mapActions.GetLayerValue) => { var l = action.itemLayer.item.data["layers"][action.itemLayer.layerIndex]; var scale = l.scale?l.scale:1; return this.itemService$.getLayerValue(action.itemLayer.item.code,action.itemLayer.layerIndex,action.x,action.y).pipe( mergeMap((v: number) => { let a=[]; if(v !== null) { if(l.renderer && l.renderer.colorMap && l.renderer.colorMap.colormapType == "manual") { l.renderer.colorMap.entries.forEach((e) => { if(e.value == v && e.label) { v=e.label; return; } }); a.push(new mapActions.GetLayerValueSuccess({date:action.itemLayer.item.dataDate,value:v,layerName:l.name,quantity:"",unit:l.unit})); } else { a.push(new mapActions.GetLayerValueSuccess({date:action.itemLayer.item.dataDate,value:v*scale,layerName:l.name,quantity:l.quantity,unit:l.unit})); } } return a; })) } ))); updateLayerValuesOnLayerAddedOrRemoved$ = createEffect(() => this.actions$.pipe( ofType(mapActions.ADDLAYER,mapActions.REMOVELAYER,mapActions.SELECTITEMSUCCESS,mapActions.SELECTTEMPORALITEMSSUCCESS, mapActions.NEXTTEMPORAL,mapActions.PREVIOUSTEMPORAL,mapActions.TOGGLELAYERVALUESENABLED,mapActions.SETLAYERINDEX,mapActions.SETSELECTEDITEMLAYER), withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesX)), withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesY)), map(([[action,x],y]) => new mapActions.SetLayerValuesLocation(x,y)) )); getLayerValues$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SETLAYERVALUESLOCATION), withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItemLayer)), withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesEnabled)), withLatestFrom(this.store$.select(mapReducers.selectGetOverlayLayers)), mergeMap(([[[action, selected], enabled],overlayLayers]) => { let layers = []; if(selected) { if(selected && (selected as TemporalItemLayer).selectedItemLayer ) { selected=(selected as TemporalItemLayer).selectedItemLayer; } layers.push(selected); } overlayLayers.forEach((ol) => { if(ol!=selected) layers.push(ol); }); let a = action as mapActions.SetLayerValuesLocation; let actions = []; if(enabled) { layers.forEach((ol) => { if("vnd.farmmaps.itemtype.shape.processed,vnd.farmmaps.itemtype.geotiff.processed".indexOf(ol.item.itemType)>=0) { actions.push(new mapActions.GetLayerValue(ol,a.x,a.y)); } }); } return actions; }))); setState$ = createEffect(() => this.actions$.pipe( ofType(mapActions.SETSTATE), withLatestFrom(this.store$.select(mapReducers.selectGetInSearch)), switchMap(([action,inSearch]) => { let a = action as mapActions.SetState; return this.getActionFromQueryState(a.queryState,inSearch); }))); escape$ = createEffect(() => this.actions$.pipe( ofType(commonActions.ESCAPE), switchMap((action) => { let a = action as commonActions.Escape; if(a.escapeKey) { return of(new mapActions.Clear()); } else { return EMPTY; } }))); setOverride$ = createEffect(() => this.actions$.pipe( ofType(ROUTER_NAVIGATED), switchMap(() => this.store$.select(selectRouteData as any)), switchMap((data: any) => { if(data && data["fm-map-map"]) { let params = data["fm-map-map"]; this.overrideSelectedItemLayer = params["overrideSelectedItemlayer"] ? params["overrideSelectedItemlayer"] : false; } else { this.overrideSelectedItemLayer = false; } return []; }) )); constructor(private actions$: Actions, private store$: Store, private folderService$: FolderService, private itemService$: ItemService,private featureIconService$:FeatureIconService,private itemTypeService$:ItemTypeService) { this._geojsonFormat = new GeoJSON(); this._wktFormat = new WKT(); } }