FarmMapsLib/projects/common-map/src/fm-map/effects/map.effects.ts

410 lines
16 KiB
TypeScript
Raw Normal View History

import { Injectable } from '@angular/core';
2023-03-15 11:18:23 +00:00
import { Store, Action, createFeatureSelector } from '@ngrx/store';
2021-07-27 14:26:57 +00:00
import { ROUTER_NAVIGATED, RouterReducerState } from '@ngrx/router-store';
import * as fromRouter from '@ngrx/router-store';
2023-03-15 11:18:23 +00:00
import { createEffect, Actions, ofType } from '@ngrx/effects';
2023-03-15 11:18:23 +00:00
import { EMPTY, Observable, of } from 'rxjs';
2021-07-27 14:26:57 +00:00
import { withLatestFrom, switchMap, map, catchError, mergeMap } from 'rxjs/operators';
2023-03-15 11:18:23 +00:00
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';
2023-03-15 11:18:23 +00:00
import { commonReducers } from '@farmmaps/common';
2023-03-15 11:18:23 +00:00
import { commonActions } from '@farmmaps/common';
2020-02-12 19:38:14 +00:00
import { IItem } from '@farmmaps/common';
import { FolderService, ItemService } from '@farmmaps/common';
import { tassign } from 'tassign';
2023-03-15 11:18:23 +00:00
import { FeatureIconService } from '../services/feature-icon.service';
2020-02-12 19:38:14 +00:00
import * as style from 'ol/style';
2023-03-15 11:18:23 +00:00
import { ItemTypeService, IQueryState } from '@farmmaps/common';
2021-03-06 13:06:31 +00:00
import { TemporalItemLayer } from '../models/item.layer'
2020-03-22 19:08:53 +00:00
2021-07-27 14:26:57 +00:00
export const getRouterState = createFeatureSelector<RouterReducerState>('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);
2020-02-12 19:38:14 +00:00
@Injectable()
export class MapEffects {
2019-12-11 09:29:47 +00:00
private _geojsonFormat: GeoJSON;
private _wktFormat: WKT;
2023-03-06 13:04:14 +00:00
private overrideSelectedItemLayer = false;
2019-12-11 09:29:47 +00:00
2023-03-15 11:18:23 +00:00
private updateFeatureGeometry(feature: Feature<Geometry>, updateEvent: commonActions.DeviceUpdateEvent): Feature<Geometry> {
2023-03-06 13:04:14 +00:00
const newFeature = feature.clone();
2023-03-15 11:18:23 +00:00
const f = this._wktFormat.readFeature(updateEvent.attributes["geometry"], {
2019-12-11 09:29:47 +00:00
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
2023-03-06 13:04:14 +00:00
const centroid = getCenter(f.getGeometry().getExtent());
2021-08-12 15:13:40 +00:00
newFeature.setId(feature.getId());
newFeature.setGeometry(new Point(centroid));
return newFeature;
}
2023-03-15 11:18:23 +00:00
init$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.INIT),
withLatestFrom(this.store$.select(commonReducers.selectGetRootItems)),
switchMap(([action, rootItems]) => {
2023-03-15 11:18:23 +00:00
const actions = [];
2023-03-06 13:04:14 +00:00
for (const rootItem of rootItems) {
2020-02-12 19:38:14 +00:00
if (rootItem.itemType == "UPLOADS_FOLDER") actions.push(new mapActions.SetParent(rootItem.code));
}
2020-02-12 19:38:14 +00:00
// initialize default feature styles
2023-03-15 11:18:23 +00:00
actions.push(new mapActions.SetStyle('file', new style.Style({
2020-02-12 19:38:14 +00:00
image: new style.Icon({
2023-03-15 11:18:23 +00:00
anchor: [0.5, 1],
scale: 0.05,
2021-01-24 08:09:43 +00:00
src: this.featureIconService$.getIconImageDataUrl("fal fa-file")
}),
2020-02-12 19:38:14 +00:00
stroke: new style.Stroke({
color: 'red',
width: 1
2021-01-26 08:12:37 +00:00
}),
fill: new style.Fill({
color: 'rgba(0, 0, 0,0)'
2020-02-12 19:38:14 +00:00
})
})));
2023-03-15 11:18:23 +00:00
actions.push(new mapActions.SetStyle('selected', new style.Style({
2020-02-12 19:38:14 +00:00
image: new style.Icon({
anchor: [0.5, 1],
scale: 0.08,
src: this.featureIconService$.getIconImageDataUrl(null)
}),
stroke: new style.Stroke({
color: 'red',
width: 3
2021-01-26 08:12:37 +00:00
}),
fill: new style.Fill({
color: 'rgba(0, 0, 0, 0)'
2020-02-12 19:38:14 +00:00
})
})));
2020-02-12 19:38:14 +00:00
return actions;
}
2021-07-27 14:26:57 +00:00
)));
2023-03-15 11:18:23 +00:00
initBaseLayers$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.INIT),
withLatestFrom(this.store$.select(mapReducers.selectGetProjection)),
2021-07-27 14:26:57 +00:00
map(([action, projection]) => new mapActions.LoadBaseLayers(projection)))
);
2023-03-15 11:18:23 +00:00
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))));
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
startSearch$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.STARTSEARCH),
2020-10-28 12:31:12 +00:00
switchMap((action) => {
2023-03-06 13:04:14 +00:00
const a = action as mapActions.StartSearch;
const startDate = a.queryState.startDate;
const endDate = a.queryState.endDate;
2023-03-15 11:18:23 +00:00
let newAction: Observable<Action>;
2020-04-22 06:59:04 +00:00
if (a.queryState.itemCode || a.queryState.parentCode || a.queryState.itemType || a.queryState.query || a.queryState.tags) {
2023-03-15 11:18:23 +00:00
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) => {
2023-03-06 13:04:14 +00:00
for (const f of features.features) {
if (f.properties && f.properties["code"]) {
f.id = f.properties["code"];
}
}
2020-10-28 12:31:12 +00:00
return of(new mapActions.StartSearchSuccess(this._geojsonFormat.readFeatures(features), a.queryState));
}
),
catchError(error => of(new commonActions.Fail(error))));
} else {
return [];
}
return newAction;
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
zoomToExtent$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.STARTSEARCHSUCCESS),
mergeMap((action: mapActions.StartSearchSuccess) => {
2023-03-15 11:18:23 +00:00
const actions = [];
2020-04-21 11:22:54 +00:00
actions.push(new commonActions.SetMenuVisible(false));
2023-03-06 13:04:14 +00:00
const extent = createEmpty();
2021-07-27 07:31:14 +00:00
if (!action.query.bboxFilter) {
2023-03-15 11:18:23 +00:00
if (extent) {
2023-03-06 13:04:14 +00:00
for (const f of action.features) {
2021-10-05 11:46:10 +00:00
extend(extent, (f as Feature<Geometry>).getGeometry().getExtent());
2020-04-21 11:22:54 +00:00
}
2023-03-15 11:18:23 +00:00
if (action.features && action.features.length > 0) {
2020-09-29 12:56:23 +00:00
actions.push(new mapActions.SetExtent(extent));
}
}
2020-04-21 11:22:54 +00:00
}
return actions;
2021-07-27 14:26:57 +00:00
})));
2020-04-21 11:22:54 +00:00
2023-03-15 11:18:23 +00:00
zoomToExtent2$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.SETFEATURES),
switchMap((action: mapActions.SetFeatures) => {
const extent = createEmpty();
if (extent) {
for (const f of action.features) {
extend(extent, (f as Feature<Geometry>).getGeometry().getExtent());
}
2023-03-15 11:18:23 +00:00
if (action.features.length > 0) return of(new mapActions.SetExtent(extent));
}
return EMPTY;
})));
2020-12-11 12:35:51 +00:00
2023-03-15 11:18:23 +00:00
hideMenu$ = createEffect(() => this.actions$.pipe(
2020-04-21 11:22:54 +00:00
ofType(mapActions.STARTSEARCHSUCCESS),
mergeMap((action: mapActions.StartSearchSuccess) => {
return of(new commonActions.SetMenuVisible(false));
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
selectItem$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.SELECTITEM),
withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItem)),
2023-03-15 11:18:23 +00:00
switchMap(([action, selectedItem]) => {
const a = action as mapActions.SelectItem;
const 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(() => { const 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 [];
}
2023-03-15 11:18:23 +00:00
}
2021-07-27 14:26:57 +00:00
)));
2023-03-15 11:18:23 +00:00
selectItemSuccessSetLayer$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.SELECTITEMSUCCESS),
map((action: mapActions.SelectItemSuccess) =>
new mapActions.SetSelectedItemLayer(action.item)
)
));
2020-12-16 17:04:39 +00:00
2023-03-15 11:18:23 +00:00
selectItemSuccess$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.SELECTITEMSUCCESS),
2023-03-15 11:18:23 +00:00
switchMap((action: mapActions.SelectItemSuccess) => {
if (!this.overrideSelectedItemLayer) {
2021-07-27 07:16:58 +00:00
return this.itemService$.getFeature(action.item.code, "EPSG:3857").pipe(
map((feature: any) => {
2023-03-06 13:04:14 +00:00
const f = this._geojsonFormat.readFeature(feature);
2021-07-27 07:16:58 +00:00
f.setId(action.item.code);
2023-03-15 11:18:23 +00:00
return new mapActions.AddFeatureSuccess(f);
2021-07-27 07:16:58 +00:00
}),
catchError(error => of(new commonActions.Fail(error))));
2021-07-27 07:16:58 +00:00
} else {
return EMPTY;
}
}
2021-07-27 14:26:57 +00:00
)));
2023-03-15 11:18:23 +00:00
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 [];
2020-02-27 16:59:00 +00:00
}
2023-03-15 11:18:23 +00:00
}
)));
2020-02-27 16:59:00 +00:00
2023-03-15 11:18:23 +00:00
uploadedItemClick$ = createEffect(() => this.actions$.pipe(
ofType(commonActions.UPLOADEDFILECLICK),
switchMap((action: commonActions.UploadedFileClick) => of(new mapActions.DoQuery(tassign(mapReducers.initialState.query.querystate, { itemCode: action.itemCode })))
2021-07-27 14:26:57 +00:00
)));
2023-03-15 11:18:23 +00:00
featureUpdate$ = createEffect(() => this.actions$.pipe(
2019-12-11 09:29:47 +00:00
ofType(commonActions.DEVICEUPDATEEVENT),
withLatestFrom(this.store$.select(mapReducers.selectGetFeatures)),
mergeMap(([action, features]) => {
2023-03-06 13:04:14 +00:00
const deviceUpdateEventAction = action as commonActions.DeviceUpdateEvent;
let feature: Feature<Geometry> = null;
for (const f of features) {
2019-12-11 09:29:47 +00:00
if (f.getId() == deviceUpdateEventAction.itemCode) {
feature = f;
break;
}
}
if (feature) {
2023-03-15 11:18:23 +00:00
return of(new mapActions.UpdateFeatureSuccess(this.updateFeatureGeometry(feature, deviceUpdateEventAction)));
} else {
return [];
}
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
itemUpdate$ = createEffect(() => this.actions$.pipe(
ofType(commonActions.ITEMCHANGEDEVENT),
withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItem)),
mergeMap(([action, selectedItem]) => {
2023-03-06 13:04:14 +00:00
const 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 => {
2023-03-15 11:18:23 +00:00
return { child, parent };
})
);
}),
map(data => new mapActions.SelectItemSuccess(data.child, data.parent)),
catchError(error => of(new commonActions.Fail(error))));
} else {
return [];
}
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
getActionFromQueryState(queryState: IQueryState, inSearch: boolean): Observable<Action> | [] {
if (!inSearch && (queryState.itemType || queryState.parentCode || queryState.itemCode || queryState.query || queryState.tags)) {
var newAction: Action;
if (queryState.itemCode && queryState.itemCode != "") {
2023-03-15 11:18:23 +00:00
newAction = new mapActions.SelectItem(queryState.itemCode);
2020-04-21 10:31:20 +00:00
} else {
2023-03-15 11:18:23 +00:00
newAction = new mapActions.StartSearch(queryState);
2020-04-21 10:31:20 +00:00
}
2020-04-21 10:31:20 +00:00
} else {
2020-10-28 12:31:12 +00:00
newAction = new mapActions.Clear();
2020-04-21 10:31:20 +00:00
}
2020-10-28 12:31:12 +00:00
return of(newAction);
}
2020-04-21 10:31:20 +00:00
2023-03-15 11:18:23 +00:00
getLayerValue$ = createEffect(() => this.actions$.pipe(
2021-03-05 16:19:30 +00:00
ofType(mapActions.GETLAYERVALUE),
2023-03-15 11:18:23 +00:00
mergeMap((action: mapActions.GetLayerValue) => {
2023-03-06 13:04:14 +00:00
const l = action.itemLayer.item.data["layers"][action.itemLayer.layerIndex];
2023-03-15 11:18:23 +00:00
const scale = l.scale ? l.scale : 1;
return this.itemService$.getLayerValue(action.itemLayer.item.code, action.itemLayer.layerIndex, action.x, action.y).pipe(
2021-03-05 17:41:09 +00:00
mergeMap((v: number) => {
2023-03-15 11:18:23 +00:00
const a = [];
if (v !== null) {
if (l.renderer && l.renderer.colorMap && l.renderer.colorMap.colormapType == "manual") {
2021-03-05 17:41:09 +00:00
l.renderer.colorMap.entries.forEach((e) => {
2023-03-15 11:18:23 +00:00
if (e.value == v && e.label) {
v = e.label;
return;
}
2021-03-05 17:41:09 +00:00
});
2023-03-15 11:18:23 +00:00
a.push(new mapActions.GetLayerValueSuccess({ date: action.itemLayer.item.dataDate, value: v, layerName: l.name, quantity: "", unit: l.unit, scale: l.scale }));
2021-03-05 17:41:09 +00:00
} else {
2023-03-15 11:18:23 +00:00
a.push(new mapActions.GetLayerValueSuccess({ date: action.itemLayer.item.dataDate, value: v * scale, layerName: l.name, quantity: l.quantity, unit: l.unit, scale: l.scale }));
2021-03-05 17:41:09 +00:00
}
}
2021-03-05 16:19:30 +00:00
return a;
}))
2023-03-15 11:18:23 +00:00
}
)));
2021-03-05 16:19:30 +00:00
2023-03-15 11:18:23 +00:00
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))
2021-07-27 14:26:57 +00:00
));
2021-03-05 16:19:30 +00:00
2023-03-15 11:18:23 +00:00
getLayerValues$ = createEffect(() => this.actions$.pipe(
2021-03-05 16:19:30 +00:00
ofType(mapActions.SETLAYERVALUESLOCATION),
withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItemLayer)),
withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesEnabled)),
withLatestFrom(this.store$.select(mapReducers.selectGetOverlayLayers)),
2023-03-15 11:18:23 +00:00
mergeMap(([[[action, selected], enabled], overlayLayers]) => {
2023-03-06 13:04:14 +00:00
const layers = [];
2023-03-15 11:18:23 +00:00
if (selected) {
if (selected && (selected as TemporalItemLayer).selectedItemLayer) {
selected = (selected as TemporalItemLayer).selectedItemLayer;
2021-03-06 13:06:31 +00:00
}
layers.push(selected);
}
2021-03-05 16:19:30 +00:00
overlayLayers.forEach((ol) => {
2023-03-15 11:18:23 +00:00
if (ol != selected) layers.push(ol);
2021-03-05 16:19:30 +00:00
});
2023-03-06 13:04:14 +00:00
const a = action as mapActions.SetLayerValuesLocation;
const actions = [];
2023-03-15 11:18:23 +00:00
if (enabled) {
2021-03-05 16:19:30 +00:00
layers.forEach((ol) => {
2023-03-15 11:18:23 +00:00
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));
}
});
}
2021-03-05 16:19:30 +00:00
return actions;
2021-07-27 14:26:57 +00:00
})));
2021-03-05 16:19:30 +00:00
2023-03-15 11:18:23 +00:00
setState$ = createEffect(() => this.actions$.pipe(
ofType(mapActions.SETSTATE),
withLatestFrom(this.store$.select(mapReducers.selectGetInSearch)),
2023-03-15 11:18:23 +00:00
switchMap(([action, inSearch]) => {
2023-03-06 13:04:14 +00:00
const a = action as mapActions.SetState;
2023-03-15 11:18:23 +00:00
return this.getActionFromQueryState(a.queryState, inSearch);
2021-07-27 14:26:57 +00:00
})));
2023-03-15 11:18:23 +00:00
escape$ = createEffect(() => this.actions$.pipe(
ofType(commonActions.ESCAPE),
switchMap((action) => {
const a = action as commonActions.Escape;
if (a.escapeKey) {
return of(new mapActions.Clear());
} else {
return EMPTY;
}
})));
2021-07-27 14:26:57 +00:00
2023-03-15 11:18:23 +00:00
setOverride$ = createEffect(() => this.actions$.pipe(
ofType(ROUTER_NAVIGATED),
switchMap(() => this.store$.select(selectRouteData as any)),
switchMap((data: any) => {
if (data && data["fm-map-map"]) {
const params = data["fm-map-map"];
this.overrideSelectedItemLayer = params["overrideSelectedItemlayer"] ? params["overrideSelectedItemlayer"] : false;
} else {
this.overrideSelectedItemLayer = false;
}
return [];
})
));
2020-10-28 12:31:12 +00:00
2023-03-15 11:18:23 +00:00
constructor(private actions$: Actions, private store$: Store<mapReducers.State>, private folderService$: FolderService, private itemService$: ItemService, private featureIconService$: FeatureIconService, private itemTypeService$: ItemTypeService) {
2019-12-11 09:29:47 +00:00
this._geojsonFormat = new GeoJSON();
this._wktFormat = new WKT();
2023-03-15 11:18:23 +00:00
}
}