Add layer values
All checks were successful
FarmMaps.Develop/FarmMapsLib/pipeline/head This commit looks good

This commit is contained in:
Willem Dantuma 2021-03-05 17:19:30 +01:00
parent 5760c2b8ea
commit 9d5cd0fa88
13 changed files with 290 additions and 20 deletions

18
package-lock.json generated
View File

@ -1865,25 +1865,25 @@
} }
}, },
"@farmmaps/common": { "@farmmaps/common": {
"version": "0.0.1-prerelease.560", "version": "0.0.1-prerelease.563",
"resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common/-/common-0.0.1-prerelease.560.tgz", "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common/-/common-0.0.1-prerelease.563.tgz",
"integrity": "sha512-87kbYb6bF2J4/IFHl77hii3ab2cbxV0TZYZHk2Td9E6WHZisp9lCASOwye72/bDNsmOaS+YYj4xCEz6eRPfr5A==", "integrity": "sha512-XsPocXZm3/glNaaRF3ojzzbwILmM6HVW6lvLKMDUH/1deUB4EAlTbcj0O1hpkArcDfBs6lRvMXkWpbyT58fxmQ==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@farmmaps/common-map": { "@farmmaps/common-map": {
"version": "0.0.1-prerelease.560", "version": "0.0.1-prerelease.563",
"resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map/-/common-map-0.0.1-prerelease.560.tgz", "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map/-/common-map-0.0.1-prerelease.563.tgz",
"integrity": "sha512-DzQYLsYU6rar+syX5DzqhUlKQ3z/2A8l2cWIiDafWMWO0nW4nEb09zmRknFiBk5q+SzuhvSmZzqndYSxA8vY0w==", "integrity": "sha512-uxjRwwh/8Q/NHF86grwNUC8b0cyHrtAHeVuOYuAtLSjy/DA9k0+C3/S6LgOnvmP0K4Sempc81BLeDvN0eG0cGA==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@farmmaps/common-map3d": { "@farmmaps/common-map3d": {
"version": "0.0.1-prerelease.560", "version": "0.0.1-prerelease.563",
"resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map3d/-/common-map3d-0.0.1-prerelease.560.tgz", "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map3d/-/common-map3d-0.0.1-prerelease.563.tgz",
"integrity": "sha512-awp3inFWxmyKm0DK5IxjyiStjBn/6B6yiMezoWjJrPue6ygfYHKGy5O2GKI1gzONS/75skeRqcl2zswBDWK2xw==", "integrity": "sha512-njlJgIjhpZll8Q1B/VSe7lwrM47Zc4V40jj8Eb2MVWwW7DrRVhf1qrqTbrNHbOzdi+0oy79/TUKQlYvmeVEkPQ==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }

View File

@ -19,9 +19,9 @@
"@angular/platform-browser": "~10.2.4", "@angular/platform-browser": "~10.2.4",
"@angular/platform-browser-dynamic": "~10.2.4", "@angular/platform-browser-dynamic": "~10.2.4",
"@angular/router": "~10.2.4", "@angular/router": "~10.2.4",
"@farmmaps/common": ">=0.0.1-prerelease.560 <0.0.1", "@farmmaps/common": ">=0.0.1-prerelease.563 <0.0.1",
"@farmmaps/common-map": ">=0.0.1-prerelease.560 <0.0.1", "@farmmaps/common-map": ">=0.0.1-prerelease.563 <0.0.1",
"@farmmaps/common-map3d": ">=0.0.1-prerelease.560 <0.0.1", "@farmmaps/common-map3d": ">=0.0.1-prerelease.563 <0.0.1",
"@microsoft/signalr": "^3.1.3", "@microsoft/signalr": "^3.1.3",
"@ng-bootstrap/ng-bootstrap": "^7.0", "@ng-bootstrap/ng-bootstrap": "^7.0",
"@ngrx/effects": "^10.0", "@ngrx/effects": "^10.0",

View File

@ -2,6 +2,7 @@ import { Action } from '@ngrx/store';
import { IMapState } from '../models/map.state'; import { IMapState } from '../models/map.state';
import { IItemLayer } from '../models/item.layer'; import { IItemLayer } from '../models/item.layer';
import { ILayervalue } from '../models/layer.value';
import { IQueryState } from '@farmmaps/common'; import { IQueryState } from '@farmmaps/common';
import { IItem } from '@farmmaps/common'; import { IItem } from '@farmmaps/common';
import { Feature,Style } from 'ol'; import { Feature,Style } from 'ol';
@ -45,6 +46,10 @@ export const SHOWLAYERSWITCHER = '[Map] ShowLayerSwitcher';
export const CLEAR = '[Map] Clear'; export const CLEAR = '[Map] Clear';
export const SETREPLACEURL = '[Map] SetReplaceUrl'; export const SETREPLACEURL = '[Map] SetReplaceUrl';
export const SETFEATURES = '[Map] SetFeatures' export const SETFEATURES = '[Map] SetFeatures'
export const SETLAYERVALUESLOCATION = '[Map] SetLayerValuesLocation'
export const TOGGLELAYERVALUESENABLED = '[Map] ToggleLayerValuesEnabled'
export const GETLAYERVALUE = '[Map] GetLayerValue'
export const GETLAYERVALUESUCCESS = '[Map] GetLayerValueSuccess'
export class Clear implements Action { export class Clear implements Action {
readonly type = CLEAR; readonly type = CLEAR;
@ -277,6 +282,30 @@ export class SetFeatures implements Action {
constructor(public features: Array<Feature>) { } constructor(public features: Array<Feature>) { }
} }
export class SetLayerValuesLocation implements Action {
readonly type = SETLAYERVALUESLOCATION;
constructor(public x:number, public y:number) { }
}
export class ToggleLayerValuesEnabled implements Action {
readonly type = TOGGLELAYERVALUESENABLED;
constructor() { }
}
export class GetLayerValue implements Action {
readonly type = GETLAYERVALUE;
constructor(public itemLayer:IItemLayer,public x:number,public y:number) { }
}
export class GetLayerValueSuccess implements Action {
readonly type = GETLAYERVALUESUCCESS;
constructor(public layervalue:ILayervalue) { }
}
export type Actions = SetMapState export type Actions = SetMapState
| Init | Init
| Clear | Clear
@ -315,5 +344,9 @@ export type Actions = SetMapState
| ShowLayerSwitcher | ShowLayerSwitcher
| SetReplaceUrl | SetReplaceUrl
| SetFeatures | SetFeatures
| SetSelectedItemLayer; | SetSelectedItemLayer
| SetLayerValuesLocation
| ToggleLayerValuesEnabled
| GetLayerValueSuccess
| GetLayerValue;

View File

@ -70,6 +70,7 @@ import {HistogramDetailsComponent} from './components/legend/histogram-details/h
import {StatisticsDetailsComponent} from './components/legend/statistics-details/statistics-details.component'; import {StatisticsDetailsComponent} from './components/legend/statistics-details/statistics-details.component';
import { ifZoomToShowDirective} from './components/if-zoom-to-show/if-zoom-to-show.directive'; import { ifZoomToShowDirective} from './components/if-zoom-to-show/if-zoom-to-show.directive';
import { ZoomToShowAlert} from './components/zoom-to-show-alert/zoom-to-show-alert.component'; import { ZoomToShowAlert} from './components/zoom-to-show-alert/zoom-to-show-alert.component';
import { LayerValuesComponent } from './components/aol/layer-values/layer-values.component';
export function LocalStorageSync(reducer: ActionReducer<any>): ActionReducer<any> { export function LocalStorageSync(reducer: ActionReducer<any>): ActionReducer<any> {
const r = function(state, action) { const r = function(state, action) {
@ -207,7 +208,8 @@ export {
HistogramDetailsComponent, HistogramDetailsComponent,
StatisticsDetailsComponent, StatisticsDetailsComponent,
ifZoomToShowDirective, ifZoomToShowDirective,
ZoomToShowAlert ZoomToShowAlert,
LayerValuesComponent
], ],
entryComponents: [ entryComponents: [
FeatureListComponent, FeatureListComponent,

View File

@ -0,0 +1,18 @@
<div #layerValues class="layer-values">
<div class="cross" *ngIf="enabled$ | async">
<div class="pointer"></div>
<div class="values-container border border-dark rounded p-2" *ngIf="(layerValues$ | async ) as layers">
<div class="lonlat pb-2 "><span class="font-weight-bold">{{lonlat$}}</span><i class="ml-2 fal fa-copy" (click)="copyToClipboard()"></i> </div>
<ul class="value-list p-0 mb-0" *ngIf="layers.length>0 ;else no_data">
<li class="border-top pt-1 pb-1 value" *ngFor="let layerValue of layers">
<div>{{layerValue.layerName}}</div>
<div>{{layerValue.date|date}}</div>
<div><span>{{layerValue.quantity}}</span> <span>{{layerValue.value}}</span><span>{{layerValue.unit}}</span></div>
</li>
</ul>
<ng-template #no_data>
<div i18n class="border-top pt-1 pb-1">No data at location</div>
</ng-template>
</div>
</div>
</div>>

View File

@ -0,0 +1,37 @@
.layer-values {
position: absolute;
left: 50%;
top: 30%;
}
.cross {
display: block;
position: relative;
width: 1em;
height: 1em;
left: -0.5em;
top: -0.5em;
}
.values-container {
position: relative;
background-color: white;
left: calc( 1em - 1px);
top: -1.3em;
min-width: 15em;
}
.value-list {
list-style: none;
}
.pointer {
position: relative;
width: 0px;
height: 0px;
left: 0.5em;
border-top: 0.5em solid transparent;
border-bottom: 0.5em solid transparent;
border-right: 0.5em solid black;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LayerValuesComponent } from './layer-values.component';
describe('LayerValuesComponent', () => {
let component: LayerValuesComponent;
let fixture: ComponentFixture<LayerValuesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LayerValuesComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LayerValuesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,59 @@
import { Component, OnInit,Input,ViewChild,ElementRef,AfterViewInit } from '@angular/core';
import {IItemLayer} from '../../../models/item.layer';
import { Store } from '@ngrx/store';
import * as mapReducers from '../../../reducers/map.reducer';
import * as mapActions from '../../../actions/map.actions';
import { MapComponent } from 'ngx-openlayers';
import { ILayervalue } from '../../../models/layer.value';
import { Observable,interval } from 'rxjs';
import { debounce } from 'rxjs/operators';
import { toLonLat } from 'ol/proj';
import { toStringHDMS } from 'ol/coordinate';
import { ClipboardService } from 'ngx-clipboard'
import {GeoJSON,WKT} from 'ol/format';
import { Point } from 'ol/geom';
@Component({
selector: 'fm-map-layer-values',
templateUrl: './layer-values.component.html',
styleUrls: ['./layer-values.component.scss']
})
export class LayerValuesComponent implements OnInit,AfterViewInit {
@ViewChild('layerValues') containerRef:ElementRef;
offsetX$:number =0;
offsetY$:number =0;
lonlat$: string="";
wkt$= "";
layerValues$:Observable<Array<ILayervalue>> = this.store.select(mapReducers.selectGetLayerValues);
enabled$:Observable<boolean> = this.store.select(mapReducers.selectGetLayerValuesEnabled);
wktFormat$:WKT;
constructor( private store: Store<mapReducers.State>,private map: MapComponent,private clipboardService$:ClipboardService) {
this.wktFormat$=new WKT();
}
ngOnInit(): void {
}
ngAfterViewInit():void {
this.offsetY$ = this.containerRef.nativeElement.offsetTop;
this.offsetX$ = this.containerRef.nativeElement.offsetLeft;
this.map.instance.on('moveend', () => {
this.updateValuesLocation();
});
}
updateValuesLocation() {
var xy = this.map.instance.getCoordinateFromPixel([this.offsetX$,this.offsetY$])
var lonlat = toLonLat(xy);
this.wkt$ = this.wktFormat$.writeGeometry(new Point(lonlat))
this.lonlat$ = toStringHDMS(lonlat);
this.store.dispatch(new mapActions.SetLayerValuesLocation(xy[0],xy[1]));
}
copyToClipboard() {
this.clipboardService$.copy(this.wkt$);
}
}

View File

@ -22,7 +22,7 @@
selectedFeature:selectedFeature$|async, selectedFeature:selectedFeature$|async,
fullscreen:fullscreen$|async fullscreen:fullscreen$|async
} as state"> } as state">
<aol-map #map (moveEnd)="handleOnMoveEnd($event)" (click)="handleOnMouseDown($event)" [ngClass]="{'panel-visible':state.panelVisible,'fullscreen':state.fullscreen}" class="map"> <aol-map #map (moveEnd)="handleOnMoveEnd($event)" (click)="handleOnMouseDown($event)" (dblClick)="handleShowLayerValues($event)" [ngClass]="{'panel-visible':state.panelVisible,'fullscreen':state.fullscreen}" class="map">
<div> <div>
</div> </div>
@ -40,6 +40,7 @@
</aol-layer-vector> </aol-layer-vector>
<router-outlet name="map-layers"></router-outlet> <router-outlet name="map-layers"></router-outlet>
<fm-map-gps-location [position]="state.position" [headingTolerance]="20" [showHeading]="devicesService.IsMobile()" [showTolerance]="devicesService.IsMobile()" [heading]="state.compassHeading"></fm-map-gps-location> <fm-map-gps-location [position]="state.position" [headingTolerance]="20" [showHeading]="devicesService.IsMobile()" [showTolerance]="devicesService.IsMobile()" [heading]="state.compassHeading"></fm-map-gps-location>
<fm-map-layer-values></fm-map-layer-values>
<div class="control-container" > <div class="control-container" >
<router-outlet name="map-controls"></router-outlet> <router-outlet name="map-controls"></router-outlet>
<fm-map-layer-switcher></fm-map-layer-switcher> <fm-map-layer-switcher></fm-map-layer-switcher>

View File

@ -383,6 +383,13 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
}); });
} }
handleShowLayerValues(event: MouseEvent) {
event.stopPropagation();
this.zone.run(() =>{
this.store.dispatch(new mapActions.ToggleLayerValuesEnabled());
});
}
handleOnDownload(event) { handleOnDownload(event) {
} }

View File

@ -3,8 +3,8 @@ import { Injectable } from '@angular/core';
import { Store, Action } from '@ngrx/store'; import { Store, Action } from '@ngrx/store';
import { Effect, Actions,ofType } from '@ngrx/effects'; import { Effect, Actions,ofType } from '@ngrx/effects';
import { EMPTY, Observable , of } from 'rxjs'; import { EMPTY, Observable , of,merge} from 'rxjs';
import { withLatestFrom, switchMap, map, catchError, mergeMap } from 'rxjs/operators'; import { withLatestFrom, switchMap, map, catchError, mergeMap,tap } from 'rxjs/operators';
import {GeoJSON,WKT} from 'ol/format'; import {GeoJSON,WKT} from 'ol/format';
import {Feature} from 'ol'; import {Feature} from 'ol';
@ -297,6 +297,55 @@ export class MapEffects {
return of(newAction); return of(newAction);
} }
@Effect()
getLayerValue$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.GETLAYERVALUE),
switchMap((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(
switchMap((v: number) => {
let a=[];
if(v) a.push(new mapActions.GetLayerValueSuccess({date:"",value:v*scale,layerName:l.name,quantity:l.quantity,unit:l.unit}));
return a;
}))
}
));
@Effect()
updateLayerValuesOnLayerAddedOrRemoved$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.ADDLAYER,mapActions.REMOVELAYER,mapActions.SELECTITEM),
withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesX)),
withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesY)),
map(([[action,x],y]) => new mapActions.SetLayerValuesLocation(x,y))
);
@Effect()
getLayerValues$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.SETLAYERVALUESLOCATION),
withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItemLayer)),
withLatestFrom(this.store$.select(mapReducers.selectGetLayerValuesEnabled)),
withLatestFrom(this.store$.select(mapReducers.selectGetOverlayLayers)),
switchMap(([[[action, selected], enabled],overlayLayers]) => {
let layers = [];
if(selected) 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;
}));
@Effect() @Effect()
setState$: Observable<Action> = this.actions$.pipe( setState$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.SETSTATE), ofType(mapActions.SETSTATE),

View File

@ -0,0 +1,7 @@
export interface ILayervalue {
date:string;
layerName:string;
unit:string;
quantity:string;
value:number;
}

View File

@ -5,6 +5,7 @@ import { IMapState} from '../models/map.state';
import { IQueryState} from '@farmmaps/common'; import { IQueryState} from '@farmmaps/common';
import { IPeriodState} from '../models/period.state'; import { IPeriodState} from '../models/period.state';
import { IStyles} from '../models/style.cache'; import { IStyles} from '../models/style.cache';
import { ILayervalue } from '../models/layer.value';
import * as mapActions from '../actions/map.actions'; import * as mapActions from '../actions/map.actions';
import {commonActions} from '@farmmaps/common'; import {commonActions} from '@farmmaps/common';
import { createSelector, createFeatureSelector } from '@ngrx/store'; import { createSelector, createFeatureSelector } from '@ngrx/store';
@ -59,7 +60,11 @@ export interface State {
showLayerSwitcher:boolean, showLayerSwitcher:boolean,
inSearch:boolean, inSearch:boolean,
inZoom:boolean, inZoom:boolean,
replaceUrl:boolean replaceUrl:boolean,
layerValuesX:number,
layerValuesY:number,
layerValuesEnabled:boolean;
layerValues: Array<ILayervalue>;
} }
export const initialState: State = { export const initialState: State = {
@ -98,7 +103,11 @@ export const initialState: State = {
showLayerSwitcher: false, showLayerSwitcher: false,
inSearch:false, inSearch:false,
inZoom:false, inZoom:false,
replaceUrl:true replaceUrl:true,
layerValuesX:0,
layerValuesY:0,
layerValuesEnabled:false,
layerValues:[]
} }
export function reducer(state = initialState, action: mapActions.Actions | commonActions.Actions | RouterNavigationAction): State { export function reducer(state = initialState, action: mapActions.Actions | commonActions.Actions | RouterNavigationAction): State {
@ -462,6 +471,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
selectedFeature: null, selectedFeature: null,
queryState: newQueryState, queryState: newQueryState,
clearEnabled: false, clearEnabled: false,
layerValuesEnabled: false,
searchCollapsed: true, searchCollapsed: true,
searchMinified: false, searchMinified: false,
features: [], features: [],
@ -484,6 +494,19 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
let a= action as mapActions.SetReplaceUrl; let a= action as mapActions.SetReplaceUrl;
return tassign(state,{replaceUrl:a.replaceUrl}); return tassign(state,{replaceUrl:a.replaceUrl});
} }
case mapActions.TOGGLELAYERVALUESENABLED: {
return tassign(state,{layerValuesEnabled:!state.layerValuesEnabled});
}
case mapActions.SETLAYERVALUESLOCATION: {
let a= action as mapActions.SetLayerValuesLocation;
return tassign(state,{layerValuesX: a.x, layerValuesY:a.y,layerValues:[]});
}
case mapActions.GETLAYERVALUESUCCESS:{
let a= action as mapActions.GetLayerValueSuccess;
let v = state.layerValues.slice(0);
v.push(a.layervalue);
return tassign(state,{layerValues:v});
}
default: { default: {
return state; return state;
} }
@ -515,6 +538,11 @@ export const getStyles = (state:State) => state.styles;
export const getShowLayerSwitcher = (state:State) => state.showLayerSwitcher; export const getShowLayerSwitcher = (state:State) => state.showLayerSwitcher;
export const getInSearch = (state:State) => state.inSearch; export const getInSearch = (state:State) => state.inSearch;
export const getState = (state:State) => {return {mapState:state.mapState,queryState:state.queryState,replaceUrl:state.replaceUrl};} export const getState = (state:State) => {return {mapState:state.mapState,queryState:state.queryState,replaceUrl:state.replaceUrl};}
export const getLayerValuesEnabled = (state:State) => state.layerValuesEnabled;
export const getLayerValues = (state:State) => state.layerValues;
export const getLayerValuesX = (state:State) => state.layerValuesX;
export const getLayerValuesY = (state:State) => state.layerValuesY;
export const selectMapState = createFeatureSelector<State>(MODULE_NAME); export const selectMapState = createFeatureSelector<State>(MODULE_NAME);
export const selectGetMapState= createSelector(selectMapState, getMapState); export const selectGetMapState= createSelector(selectMapState, getMapState);
@ -542,5 +570,9 @@ export const selectGetStyles = createSelector(selectMapState, getStyles);
export const selectGetShowLayerSwitcher = createSelector(selectMapState,getShowLayerSwitcher); export const selectGetShowLayerSwitcher = createSelector(selectMapState,getShowLayerSwitcher);
export const selectGetInSearch = createSelector(selectMapState,getInSearch); export const selectGetInSearch = createSelector(selectMapState,getInSearch);
export const selectGetState = createSelector(selectMapState,getState); export const selectGetState = createSelector(selectMapState,getState);
export const selectGetLayerValuesEnabled = createSelector(selectMapState,getLayerValuesEnabled);
export const selectGetLayerValues = createSelector(selectMapState,getLayerValues);
export const selectGetLayerValuesX = createSelector(selectMapState,getLayerValuesX);
export const selectGetLayerValuesY = createSelector(selectMapState,getLayerValuesY);