Merge branch 'feature/Temporal_item' into develop
All checks were successful
FarmMaps.Develop/FarmMapsLib/develop This commit looks good

This commit is contained in:
Willem Dantuma 2020-03-03 10:22:27 +01:00
commit 141b5055d7
12 changed files with 489 additions and 166 deletions

View File

@ -5,6 +5,7 @@ import { IItemLayer } from '../models/item.layer';
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';
import { IListItem } from 'dist/common/public-api';
export const SETSTATE = '[Map] SetState'; export const SETSTATE = '[Map] SetState';
export const SETMAPSTATE = '[Map] MapState'; export const SETMAPSTATE = '[Map] MapState';
@ -16,6 +17,10 @@ export const STARTSEARCHSUCCESS = '[Map] StartSearchSuccess';
export const SELECTFEATURE = '[Map] SelectFeature'; export const SELECTFEATURE = '[Map] SelectFeature';
export const SELECTITEM = '[Map] SelectItem'; export const SELECTITEM = '[Map] SelectItem';
export const SELECTITEMSUCCESS = '[Map] SelectItemSuccess'; export const SELECTITEMSUCCESS = '[Map] SelectItemSuccess';
export const SELECTTEMPORALITEMSSUCCESS = '[Map] SelectTemporalItemsSuccess';
export const NEXTTEMPORAL = '[Map] NextTemporal';
export const PREVIOUSTEMPORAL = '[Map] PreviousTemporal';
export const SELECTTEMPORAL = '[Map] SelectTemporal';
export const ADDFEATURESUCCESS = '[Map] AddFeatureSuccess'; export const ADDFEATURESUCCESS = '[Map] AddFeatureSuccess';
export const UPDATEFEATURESUCCESS = '[Map] UpdateFeatureSuccess'; export const UPDATEFEATURESUCCESS = '[Map] UpdateFeatureSuccess';
export const EXPANDSEARCH = '[Map] ExpandSearch'; export const EXPANDSEARCH = '[Map] ExpandSearch';
@ -97,6 +102,30 @@ export class SelectItemSuccess implements Action {
constructor(public item: IItem) { } constructor(public item: IItem) { }
} }
export class SelectTemporalItemsSuccess implements Action {
readonly type = SELECTTEMPORALITEMSSUCCESS;
constructor(public temporalItems: IItem[]) { }
}
export class NextTemporal implements Action {
readonly type = NEXTTEMPORAL;
constructor() { }
}
export class PreviousTemporal implements Action {
readonly type = PREVIOUSTEMPORAL;
constructor() { }
}
export class SelectTemporal implements Action {
readonly type = SELECTTEMPORAL;
constructor(item:IItem) { }
}
export class AddFeatureSuccess implements Action { export class AddFeatureSuccess implements Action {
readonly type = ADDFEATURESUCCESS; readonly type = ADDFEATURESUCCESS;
@ -224,6 +253,10 @@ export type Actions = SetMapState
| SelectFeature | SelectFeature
| SelectItem | SelectItem
| SelectItemSuccess | SelectItemSuccess
| SelectTemporalItemsSuccess
| NextTemporal
| PreviousTemporal
| SelectTemporal
| AddFeatureSuccess | AddFeatureSuccess
| UpdateFeatureSuccess | UpdateFeatureSuccess
| ExpandSearch | ExpandSearch

View File

@ -34,6 +34,7 @@ import { AbstractItemListComponent,ItemListComponent} from './components/item-li
import { AbstractSelectedItemComponent, SelectedItemComponent } from './components/selected-item/selected-item.component'; import { AbstractSelectedItemComponent, SelectedItemComponent } from './components/selected-item/selected-item.component';
import { SelectedItemCropfieldComponent } from './components/selected-item-cropfield/selected-item-cropfield.component'; import { SelectedItemCropfieldComponent } from './components/selected-item-cropfield/selected-item-cropfield.component';
import { SelectedItemGeotiffComponent } from './components/selected-item-geotiff/selected-item-geotiff.component'; import { SelectedItemGeotiffComponent } from './components/selected-item-geotiff/selected-item-geotiff.component';
import { SelectedItemTemporalComponent} from './components/selected-item-temporal/selected-item-temporal.component';
import {SelectedItemShapeComponent } from './components/selected-item-shape/selected-item-shape.component'; import {SelectedItemShapeComponent } from './components/selected-item-shape/selected-item-shape.component';
import { SelectedItemContainerComponent } from './components/selected-item-container/selected-item-container.component'; import { SelectedItemContainerComponent } from './components/selected-item-container/selected-item-container.component';
import { AbstractFeatureListFeatureComponent, FeatureListFeatureComponent } from './components/feature-list-feature/feature-list-feature.component'; import { AbstractFeatureListFeatureComponent, FeatureListFeatureComponent } from './components/feature-list-feature/feature-list-feature.component';
@ -116,6 +117,7 @@ export {
SelectedItemComponent, SelectedItemComponent,
SelectedItemCropfieldComponent, SelectedItemCropfieldComponent,
SelectedItemGeotiffComponent, SelectedItemGeotiffComponent,
SelectedItemTemporalComponent,
SelectedItemShapeComponent, SelectedItemShapeComponent,
ItemListItemComponent, ItemListItemComponent,
ItemListItemContainerComponent, ItemListItemContainerComponent,
@ -182,6 +184,7 @@ export {
SelectedItemComponent, SelectedItemComponent,
SelectedItemCropfieldComponent, SelectedItemCropfieldComponent,
SelectedItemGeotiffComponent, SelectedItemGeotiffComponent,
SelectedItemTemporalComponent,
SelectedItemShapeComponent, SelectedItemShapeComponent,
ItemListItemComponent, ItemListItemComponent,
ItemListItemContainerComponent, ItemListItemContainerComponent,
@ -202,6 +205,7 @@ export {
SelectedItemComponent, SelectedItemComponent,
SelectedItemCropfieldComponent, SelectedItemCropfieldComponent,
SelectedItemGeotiffComponent, SelectedItemGeotiffComponent,
SelectedItemTemporalComponent,
SelectedItemShapeComponent, SelectedItemShapeComponent,
ItemListComponent, ItemListComponent,
ItemListItemComponent, ItemListItemComponent,
@ -222,6 +226,7 @@ export {
SelectedItemComponent, SelectedItemComponent,
SelectedItemCropfieldComponent, SelectedItemCropfieldComponent,
SelectedItemGeotiffComponent, SelectedItemGeotiffComponent,
SelectedItemTemporalComponent,
SelectedItemShapeComponent, SelectedItemShapeComponent,
ItemListItemComponent, ItemListItemComponent,
ItemListItemContainerComponent, ItemListItemContainerComponent,
@ -261,6 +266,7 @@ export class AppCommonMapModule {
{ provide: AbstractSelectedItemComponent, useClass: SelectedItemComponent, multi: true }, { provide: AbstractSelectedItemComponent, useClass: SelectedItemComponent, multi: true },
{ provide: AbstractSelectedItemComponent, useClass: SelectedItemCropfieldComponent, multi: true }, { provide: AbstractSelectedItemComponent, useClass: SelectedItemCropfieldComponent, multi: true },
{ provide: AbstractSelectedItemComponent, useClass: SelectedItemGeotiffComponent, multi: true }, { provide: AbstractSelectedItemComponent, useClass: SelectedItemGeotiffComponent, multi: true },
{ provide: AbstractSelectedItemComponent, useClass: SelectedItemTemporalComponent, multi: true },
{ provide: AbstractSelectedItemComponent, useClass: SelectedItemShapeComponent, multi: true }, { provide: AbstractSelectedItemComponent, useClass: SelectedItemShapeComponent, multi: true },
{ provide: AbstractItemListItemComponent, useClass: ItemListItemComponent, multi: true }, { provide: AbstractItemListItemComponent, useClass: ItemListItemComponent, multi: true },
{ provide: AbstractItemListComponent, useClass: ItemListComponent, multi: true } { provide: AbstractItemListComponent, useClass: ItemListComponent, multi: true }

View File

@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http";
import { LayerVectorComponent, LayerTileComponent, LayerGroupComponent, MapComponent } from 'ngx-openlayers'; import { LayerVectorComponent, LayerTileComponent, LayerGroupComponent, MapComponent } from 'ngx-openlayers';
import { ItemService } from '@farmmaps/common'; import { ItemService } from '@farmmaps/common';
import { AppConfig } from '@farmmaps/common'; import { AppConfig } from '@farmmaps/common';
import { IItemLayer} from '../../../models/item.layer'; import { IItemLayer,ItemLayer, ITemporalItemLayer} from '../../../models/item.layer';
import { ILayerData} from '../../../models/layer.data'; import { ILayerData} from '../../../models/layer.data';
import { IRenderoutputTiles,IRenderoutputImage,IGradientstop,ILayer,IHistogram} from '../../../models/color.map'; import { IRenderoutputTiles,IRenderoutputImage,IGradientstop,ILayer,IHistogram} from '../../../models/color.map';
import {Extent} from 'ol/extent'; import {Extent} from 'ol/extent';
@ -19,6 +19,7 @@ import VectorTileSource from 'ol/source/VectorTile';
import VectorTileLayer from 'ol/layer/VectorTile'; import VectorTileLayer from 'ol/layer/VectorTile';
import {GeoJSON,MVT} from 'ol/format'; import {GeoJSON,MVT} from 'ol/format';
import { from } from 'rxjs'; import { from } from 'rxjs';
import { IItem } from 'dist/common/public-api';
@Component({ @Component({
selector: 'fm-map-item-layers', selector: 'fm-map-item-layers',
@ -86,83 +87,95 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
}); });
} }
createLayer(itemLayer: IItemLayer): Layer { createGeotiffLayer(item:IItem,itemLayer:IItemLayer):Layer {
var layer: Layer = null;
var layerIndex = -1; var layerIndex = -1;
if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') { var layer: Layer = null;
layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index; layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index;
let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` });
layer = new Tile({ source: source });
var data = item.data;
var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null;
if (l && l.rendering && l.rendering.renderoutputType == "Tiles") {
var rt = l.rendering as IRenderoutputTiles;
let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` });
layer = new Tile({ source: source }); layer = new Tile({ source: source });
var data = itemLayer.item.data; }
var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null; if (l && l.rendering && l.rendering.renderoutputType == "Image") {
if (l && l.rendering && l.rendering.renderoutputType == "Tiles") { var ri = l.rendering as IRenderoutputImage;
var rt = l.rendering as IRenderoutputTiles; let projection = new Projection({
let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); code: 'image',
layer = new Tile({ source: source }); units: 'pixels',
} extent: ri.extent
if (l && l.rendering && l.rendering.renderoutputType == "Image") { });
var ri = l.rendering as IRenderoutputImage; let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${item.code}/mapimage/${layerIndex}?v=${item.updated.getTime()}` });
let projection = new Projection({ layer = new Image({ source: source });
code: 'image', }
units: 'pixels', return layer;
extent: ri.extent }
});
let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/mapimage/${layerIndex}?v=${itemLayer.item.updated.getTime()}` }); createShapeLayer(item:IItem,itemLayer:IItemLayer):Layer {
layer = new Image({ source: source }); var layerIndex = -1;
} var layer: Layer = null;
} else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') { layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index;
var data = itemLayer.item.data; var data = item.data;
layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index; var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null;
var l = itemLayer.item.data.layers[layerIndex]; if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") {
if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") { var rt = item.data.layers[layerIndex].rendering as IRenderoutputTiles;
var rt = itemLayer.item.data.layers[layerIndex].rendering as IRenderoutputTiles; layer = new VectorTileLayer({
layer = new VectorTileLayer({ declutter: true,
declutter: true, source: new VectorTileSource({
source: new VectorTileSource({ maxZoom: rt.maxzoom,
maxZoom: rt.maxzoom, minZoom: rt.minzoom,
minZoom: rt.minzoom, format: new MVT(),
format: new MVT(), url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${item.updated.getTime()}`
url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${itemLayer.item.updated.getTime()}` }),
}), style: (feature) => {
style: (feature) => { return this.getColorFromGradient(l, feature);
return this.getColorFromGradient(l, feature); }
} })
} else if (l && l.rendering && l.rendering.renderoutputType == "Tiles") {
var rt = l.rendering as IRenderoutputTiles;
layer = new Tile({
source: new XYZ({
maxZoom: rt.maxzoom,
minZoom: rt.minzoom,
url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}`
}) })
} else if (l && l.rendering && l.rendering.renderoutputType == "Tiles") { });
var rt = l.rendering as IRenderoutputTiles; } else {
layer = new Tile({ let __this = this;
source: new XYZ({ let format = new GeoJSON();
maxZoom: rt.maxzoom, let source = new VectorSource({
minZoom: rt.minzoom, strategy: loadingstrategy.bbox,
url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` loader: function (extent: Extent, resolution: number, projection: Projection) {
}) var source = this as VectorSource;
}); __this.itemService.getItemFeatures(item.code, extent, projection.getCode(), layerIndex).subscribe(function (data) {
} else { var features = format.readFeatures(data);
let __this = this; for (let f of features) {
let format = new GeoJSON(); if (f.get("code")) {
let source = new VectorSource({ f.setId(f.get("code"));
strategy: loadingstrategy.bbox,
loader: function (extent: Extent, resolution: number, projection: Projection) {
var source = this as VectorSource;
__this.itemService.getItemFeatures(itemLayer.item.code, extent, projection.getCode(), layerIndex).subscribe(function (data) {
var features = format.readFeatures(data);
for (let f of features) {
if (f.get("code")) {
f.setId(f.get("code"));
}
} }
source.addFeatures(features); }
}); source.addFeatures(features);
} });
}); }
layer = new VectorLayer({ });
source: source, layer = new VectorLayer({
style: (feature) => { source: source,
var key = feature.get("color"); style: (feature) => {
if (!this.styleCache[key]) { var key = feature.get("color");
var color = feature.get("color"); if (!this.styleCache[key]) {
this.styleCache[key] = new style.Style( var color = feature.get("color");
{ this.styleCache[key] = new style.Style(
{
fill: new style.Fill({
color: color
}),
stroke: new style.Stroke({
color: color,
width: 1.25
}),
image: new style.Circle({
fill: new style.Fill({ fill: new style.Fill({
color: color color: color
}), }),
@ -170,50 +183,58 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
color: color, color: color,
width: 1.25 width: 1.25
}), }),
image: new style.Circle({ radius: 5
fill: new style.Fill({ }),
color: color }
}), )
stroke: new style.Stroke({
color: color,
width: 1.25
}),
radius: 5
}),
}
)
}
return this.styleCache[key];
} }
}); return this.styleCache[key];
}
});
}
return layer;
}
createExternalLayer(item:IItem,itemLayer:IItemLayer):Layer {
let data = item.data as ILayerData;
var layer: Layer = null;
switch (data.interfaceType) {
case 'OSM': {
let source = new OSM();
layer = new Tile({ source: source });
break;
} }
case 'BingMaps': {
let source = new BingMaps(data.options);
layer = new Tile({ source: source });
break;
}
case 'TileWMS': {
let source = new TileWMS(data.options);
layer = new Tile({ source: source });
break;
}
case 'TileArcGISRest': {
let source = new TileArcGISRest(data.options);
layer = new Tile({ source: source });
break;
}
default: {
break;
}
}
return layer;
}
createLayer(itemLayer: IItemLayer): Layer {
var layer: Layer = null;
var layerIndex = -1;
if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') {
layer = this.createGeotiffLayer(itemLayer.item,itemLayer);
} else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') {
layer = this.createShapeLayer(itemLayer.item,itemLayer);
} else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') { } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') {
let data = itemLayer.item.data as ILayerData; layer = this.createExternalLayer(itemLayer.item,itemLayer);
switch (data.interfaceType) {
case 'OSM': {
let source = new OSM();
layer = new Tile({ source: source });
break;
}
case 'BingMaps': {
let source = new BingMaps(data.options);
layer = new Tile({ source: source });
break;
}
case 'TileWMS': {
let source = new TileWMS(data.options);
layer = new Tile({ source: source });
break;
}
case 'TileArcGISRest': {
let source = new TileArcGISRest(data.options);
layer = new Tile({ source: source });
break;
}
default: {
break;
}
}
} }
if (layer) { if (layer) {
let geometry = new GeoJSON().readGeometry(itemLayer.item.geometry); let geometry = new GeoJSON().readGeometry(itemLayer.item.geometry);
@ -229,33 +250,51 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
this.updateLayers(this.itemLayers); this.updateLayers(this.itemLayers);
} }
updateLayers(itemLayers: IItemLayer[]) { addOrUpdateOlLayer(itemLayer:IItemLayer,index:number):Layer {
if (itemLayers) { if(!itemLayer) return null;
var olLayers = this.instance.getLayers(); var olLayers = this.instance.getLayers();
itemLayers.forEach((itemLayer, index) => { var layer = itemLayer.layer;
let olIndex = olLayers.getArray().indexOf(layer);
if (olIndex < 0) {
// New layer: we add it to the map
layer = this.createLayer(itemLayer);
if (layer) {
olLayers.insertAt(index, layer);
}
} else if (index !== olIndex) {
// layer has moved inside the layers list
olLayers.removeAt(olIndex);
olLayers.insertAt(index, layer);
}
if(layer) {
itemLayer.layer = layer;
layer.setOpacity(itemLayer.opacity);
layer.setVisible(itemLayer.visible);
}
return layer;
}
var layer = itemLayer.layer; updateLayers(itemLayers: IItemLayer[]) {
let olIndex = olLayers.getArray().indexOf(layer); let newLayers: IItemLayer[] = [];
if (olIndex < 0) { if (itemLayers) {
// New layer: we add it to the map itemLayers.forEach((itemLayer, index) => {
layer = this.createLayer(itemLayer); if(itemLayer.item.itemType == 'vnd.farmmaps.itemtype.temporal') {
if (layer) { let il = itemLayer as ITemporalItemLayer;
itemLayer.layer = layer; let previousLayer = this.addOrUpdateOlLayer(il.previousItemLayer,newLayers.length);
} if(previousLayer) newLayers.push(previousLayer);
olLayers.insertAt(index, layer); let selectedLayer = this.addOrUpdateOlLayer(il.selectedItemLayer,newLayers.length);
} else if (index !== olIndex) { if(selectedLayer) newLayers.push(selectedLayer);
// layer has moved inside the layers list let nextLayer = this.addOrUpdateOlLayer(il.nextItemLayer,newLayers.length);
olLayers.removeAt(olIndex); if(nextLayer) newLayers.push(nextLayer);
olLayers.insertAt(index, layer); } else {
let layer = this.addOrUpdateOlLayer(itemLayer,newLayers.length);
if(layer) newLayers.push(layer);
} }
layer.setOpacity(itemLayer.opacity);
layer.setVisible(itemLayer.visible);
}); });
// Remove the layers that have disapeared from childrenLayers // Remove the layers that have disapeared from childrenLayers
if (olLayers.getLength() > itemLayers.length) { var olLayers = this.instance.getLayers();
for (let i = itemLayers.length; i < olLayers.getLength(); i++) { while(olLayers.getLength() > newLayers.length) {
olLayers.removeAt(i); olLayers.removeAt(newLayers.length);
}
} }
} }
} }

View File

@ -57,7 +57,7 @@
</div> </div>
<div *ngIf="state.selectedItem;let item"> <div *ngIf="state.selectedItem;let item">
<fm-map-selected-item-container [item]="item"></fm-map-selected-item-container> <fm-map-selected-item-container [item]="item" [itemLayer]="state.selectedItemLayer"></fm-map-selected-item-container>
</div> </div>
<div *ngIf="state.features.length == 0" class="no-results m-2"> <div *ngIf="state.features.length == 0" class="no-results m-2">
<div *ngIf="state.queryState.query">Cannot find <span>{{state.queryState?.query}}</span></div> <div *ngIf="state.queryState.query">Cannot find <span>{{state.queryState?.query}}</span></div>

View File

@ -2,6 +2,7 @@ import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleCh
import { IItem } from '@farmmaps/common'; import { IItem } from '@farmmaps/common';
import { AbstractSelectedItemComponent, SelectedItemComponent } from '../selected-item/selected-item.component'; import { AbstractSelectedItemComponent, SelectedItemComponent } from '../selected-item/selected-item.component';
import { WidgetHostDirective } from '../widget-host/widget-host.directive'; import { WidgetHostDirective } from '../widget-host/widget-host.directive';
import { IItemLayer } from '../../models/item.layer';
@Component({ @Component({
@ -15,6 +16,7 @@ export class SelectedItemContainerComponent {
} }
@Input() item: IItem; @Input() item: IItem;
@Input() itemLayer:IItemLayer
@ViewChild(WidgetHostDirective, { static: true }) widgetHost: WidgetHostDirective; @ViewChild(WidgetHostDirective, { static: true }) widgetHost: WidgetHostDirective;
@ -41,11 +43,15 @@ export class SelectedItemContainerComponent {
const componentRef = viewContainerRef.createComponent(componentFactory); const componentRef = viewContainerRef.createComponent(componentFactory);
(<AbstractSelectedItemComponent>componentRef.instance).item = this.item; (<AbstractSelectedItemComponent>componentRef.instance).item = this.item;
(<AbstractSelectedItemComponent>componentRef.instance).itemLayer = this.itemLayer;
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes["item"] && changes["item"].currentValue) { if (changes["item"] && changes["item"].currentValue) {
this.loadComponent(); this.loadComponent();
} }
if (changes["itemLayer"] && changes["itemLayer"].currentValue) {
this.loadComponent();
}
} }
} }

View File

@ -0,0 +1,30 @@
<div class="spacer"></div>
<div *ngIf="selectedItem();let item">
<div class="card border-0">
<div class="card-body">
<div class="mb-2"><a href="#" (click)="handleBackToList($event)" i18n>Back</a></div>
<div class="card menu-card">
<h1>{{item.name}}</h1>
<h3>{{item.dataDate|date}}</h3>
</div>
<div class="legend-container" *ngIf="item?.data.layers;let layers">
<div class="card menu-card">
<div *ngIf="layers.length>1">
<select (change)="onLayerChanged($event.target.value)">
<option *ngFor="let l of layers;let layerIndex = index" [value]="layerIndex">{{l.name}}</option>
</select>
</div>
<fm-map-layer-legend [layer]="layers[selectedLayer]" [histogramenabled]="true"></fm-map-layer-legend>
</div>
</div>
<div class="card menu-card">
<ul class="p-0 mt-2">
<li *ngIf="item.isEditable"><a href="#" class="mt-1 mr-1" (click)="handleOnEdit(item)" i18n><i class="fa fa-pencil" aria-hidden="true" title="Edit"></i> Edit</a></li>
<li *ngIf="itemTypeService.isLayer(item)"><a href="#" (click)="handleAddAsLayer(item)" class="mt-1 mr-1" i18n><i class="fa fa-eye" aria-hidden="true" title="Add as layer"></i> Add as overlay</a></li>
<li *ngIf="hasNext()"><a href="#" (click)="handleNextTemporal($event)" class="mt-1 mr-1" i18n>Next <span>({{nextDate()|date}})</span></a></li>
<li *ngIf="hasPrevious()"><a href="#" (click)="handlePreviousTemporal($event)" class="mt-1 mr-1" i18n>Previous <span>({{previousDate()|date}})</span></a></li>
</ul>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,41 @@
@import "~bootstrap/scss/bootstrap.scss";
.big-icon {
width: 100%;
color: white;
font-size: 9rem;
padding: 3rem;
text-align: center;
}
.card-title {
font-size: 1rem;
}
ul {
list-style:none;
}
li {
margin-top:1rem;
}
.spacer {
display:none;
height:4rem;
}
@media screen and (min-width:44rem) {
.spacer {
display:block;
}
}
.menu-card {
margin-left: -7px;
padding-left: 7px;
margin-right: -7px;
padding-right: 7px;
margin-bottom: 7px;
}

View File

@ -0,0 +1,73 @@
import { Component, Injectable } from '@angular/core';
import { Location } from '@angular/common';
import { Store } from '@ngrx/store';
import * as mapReducers from '../../reducers/map.reducer';
import { commonReducers, ItemTypeService, ItemService, FolderService,IItem } from '@farmmaps/common';
import { Router } from '@angular/router';
import { ForItemType } from '../for-item/for-itemtype.decorator';
import { AbstractSelectedItemComponent } from '../selected-item/selected-item.component';
import { ITemporalItemLayer} from '../../models/item.layer';
import * as mapActions from '../../actions/map.actions';
@ForItemType("vnd.farmmaps.itemtype.temporal")
@Injectable()
@Component({
selector: 'fm-map-selected-item-temporal',
templateUrl: './selected-item-temporal.component.html',
styleUrls: ['./selected-item-temporal.component.scss']
})
export class SelectedItemTemporalComponent extends AbstractSelectedItemComponent {
constructor(store: Store<mapReducers.State | commonReducers.State>, itemTypeService: ItemTypeService, location: Location, router: Router, private itemService$: ItemService,private folderService$: FolderService) {
super(store, itemTypeService,location,router);
}
public selectedLayer: number = 0;
onLayerChanged(layerIndex: number) {
this.selectedLayer = layerIndex;
this.store.dispatch(new mapActions.SetLayerIndex(layerIndex));
}
hasNext():boolean {
let temporalItemLayer = this.itemLayer as ITemporalItemLayer;
return temporalItemLayer && temporalItemLayer.nextItemLayer != null;
}
nextDate():Date {
let temporalItemLayer = this.itemLayer as ITemporalItemLayer;
if(temporalItemLayer.nextItemLayer.item)
return temporalItemLayer.nextItemLayer.item.dataDate;
return null;
}
hasPrevious():boolean {
let temporalItemLayer = this.itemLayer as ITemporalItemLayer;
return temporalItemLayer && temporalItemLayer.previousItemLayer != null;
}
previousDate():Date {
let temporalItemLayer = this.itemLayer as ITemporalItemLayer;
if(temporalItemLayer.previousItemLayer.item)
return temporalItemLayer.previousItemLayer.item.dataDate;
return null;
}
handleNextTemporal(event:MouseEvent) {
this.store.dispatch(new mapActions.NextTemporal());
event.preventDefault();
}
handlePreviousTemporal(event:MouseEvent) {
this.store.dispatch(new mapActions.PreviousTemporal());
event.preventDefault();
}
selectedItem():IItem {
let temporalItemLayer = this.itemLayer as ITemporalItemLayer;
if(temporalItemLayer && temporalItemLayer.selectedItemLayer) {
return temporalItemLayer.selectedItemLayer.item;
}
return null;
}
}

View File

@ -5,11 +5,13 @@ import * as mapReducers from '../../reducers/map.reducer';
import {AppConfig, commonReducers, IItem, ItemTypeService} from '@farmmaps/common'; import {AppConfig, commonReducers, IItem, ItemTypeService} from '@farmmaps/common';
import * as mapActions from '../../actions/map.actions'; import * as mapActions from '../../actions/map.actions';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import { IItemLayer } from '../../models/item.layer';
@Injectable() @Injectable()
export abstract class AbstractSelectedItemComponent { export abstract class AbstractSelectedItemComponent {
@Input() item: IItem @Input() item: IItem
@Input() itemLayer: IItemLayer
constructor(public store: Store<mapReducers.State | commonReducers.State>, public itemTypeService: ItemTypeService, private location: Location, private router: Router) { constructor(public store: Store<mapReducers.State | commonReducers.State>, public itemTypeService: ItemTypeService, private location: Location, private router: Router) {
} }

View File

@ -172,6 +172,24 @@ export class MapEffects {
} }
)); ));
@Effect()
selectItemSuccessTemporal$: Observable<Action> = 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) =>
-(b.dataDate.getTime() - a.dataDate.getTime())
)
)),
catchError(error => of(new commonActions.Fail(error))));
} else {
return [];
}
}
));
@Effect() @Effect()
uploadedItemClick$: Observable<Action> = this.actions$.pipe( uploadedItemClick$: Observable<Action> = this.actions$.pipe(
ofType(commonActions.UPLOADEDFILECLICK), ofType(commonActions.UPLOADEDFILECLICK),

View File

@ -1,26 +1,49 @@
import { IItem } from '@farmmaps/common'; import { IItem,IListItem } from '@farmmaps/common';
import {Layer} from 'ol/layer'; import {Layer} from 'ol/layer';
export interface IItemLayer { export interface IItemLayer {
item: IItem, item: IItem,
layer: Layer, layer: Layer,
visible: boolean, visible: boolean,
legendVisible:boolean, legendVisible:boolean,
projection: string, projection: string,
opacity: number, opacity: number,
layerIndex:number layerIndex:number
} }
export class ItemLayer implements IItemLayer { export class ItemLayer implements IItemLayer {
public item: IItem; public item: IItem;
public layer: Layer = null; public layer: Layer = null;
public visible: boolean = true; public visible: boolean = true;
public legendVisible: boolean = false; public legendVisible: boolean = false;
public projection: string; public projection: string;
public opacity: number = 1; public opacity: number = 1;
public layerIndex: number = -1; public layerIndex: number = -1;
constructor(item:IItem) { constructor(item:IItem,opacity:number = 1, visible:boolean = true) {
this.item = item; this.item = item;
} this.opacity = opacity;
} this.visible = visible;
}
}
export interface ITemporalItemLayer extends IItemLayer {
previousItemLayer: IItemLayer,
selectedItemLayer: IItemLayer,
nextItemLayer: IItemLayer,
temporalItems: IItem[],
}
export class TemporalItemLayer extends ItemLayer implements ITemporalItemLayer {
public previousItemLayer:IItemLayer = null;
public selectedItemLayer:IItemLayer =null;
public nextItemLayer:IItemLayer = null;
public temporalItems:IItem[] = [];
constructor(item:IItem,opacity:number = 1, visible:boolean = true) {
super(item,opacity,visible)
}
}

View File

@ -1,6 +1,6 @@
import { tassign } from 'tassign'; import { tassign } from 'tassign';
import { IItem,Item } from '@farmmaps/common'; import { IItem,Item } from '@farmmaps/common';
import { IItemLayer,ItemLayer} from '../models/item.layer'; import { IItemLayer,ItemLayer,ITemporalItemLayer,TemporalItemLayer} from '../models/item.layer';
import { IMapState} from '../models/map.state'; 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';
@ -164,6 +164,8 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
var itemLayer = null; var itemLayer = null;
if (a.item && "vnd.farmmaps.itemtype.layer,vnd.farmmaps.itemtype.shape.processed,vnd.farmmaps.itemtype.geotiff.processed".indexOf(a.item.itemType) >=0 ) { if (a.item && "vnd.farmmaps.itemtype.layer,vnd.farmmaps.itemtype.shape.processed,vnd.farmmaps.itemtype.geotiff.processed".indexOf(a.item.itemType) >=0 ) {
itemLayer = new ItemLayer(a.item); itemLayer = new ItemLayer(a.item);
} else if (a.item && a.item.itemType == "vnd.farmmaps.itemtype.temporal") {
itemLayer = new TemporalItemLayer(a.item);
} }
return tassign(state, { return tassign(state, {
selectedItem: a.item, selectedItem: a.item,
@ -175,6 +177,56 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
queryState: tassign(state.queryState, {itemCode:a.item ? a.item.code:null}) queryState: tassign(state.queryState, {itemCode:a.item ? a.item.code:null})
}); });
} }
case mapActions.SELECTTEMPORALITEMSSUCCESS:{
let a = action as mapActions.SelectTemporalItemsSuccess;
let selectedItemLayer=tassign(state.selectedItemLayer) as TemporalItemLayer;
selectedItemLayer.temporalItems = a.temporalItems;
selectedItemLayer.selectedItemLayer = a.temporalItems.length>0?new ItemLayer(a.temporalItems[a.temporalItems.length-1]):null;
selectedItemLayer.previousItemLayer = a.temporalItems.length>1?new ItemLayer(a.temporalItems[a.temporalItems.length-2],0):null;
selectedItemLayer.nextItemLayer = null;
return tassign(state,{selectedItemLayer:tassign(state.selectedItemLayer,selectedItemLayer as ItemLayer)});
}
case mapActions.NEXTTEMPORAL: {
let temporalLayer = state.selectedItemLayer as ITemporalItemLayer;
if(temporalLayer.temporalItems && temporalLayer.temporalItems.length>0) {
let index = temporalLayer.temporalItems.indexOf(temporalLayer.selectedItemLayer.item);
if(index == (temporalLayer.temporalItems.length-1)) {
return state;
} else {
temporalLayer.previousItemLayer = temporalLayer.selectedItemLayer;
if( temporalLayer.previousItemLayer) temporalLayer.previousItemLayer.opacity=0;
temporalLayer.selectedItemLayer = temporalLayer.nextItemLayer;
if( temporalLayer.selectedItemLayer) temporalLayer.selectedItemLayer.opacity=1;
temporalLayer.nextItemLayer = index+2 < temporalLayer.temporalItems.length ? new ItemLayer(temporalLayer.temporalItems[index+2],0):null;
if( temporalLayer.nextItemLayer) temporalLayer.nextItemLayer.opacity=0;
return tassign(state,{selectedItemLayer:tassign(state.selectedItemLayer,temporalLayer as ItemLayer)});
}
} else {
return state;
}
}
case mapActions.PREVIOUSTEMPORAL: {
let temporalLayer = state.selectedItemLayer as ITemporalItemLayer;
if(temporalLayer.temporalItems && temporalLayer.temporalItems.length>0) {
let index = temporalLayer.temporalItems.indexOf(temporalLayer.selectedItemLayer.item);
if(index == 0) {
return state;
} else {
temporalLayer.nextItemLayer = temporalLayer.selectedItemLayer;
if( temporalLayer.nextItemLayer) temporalLayer.nextItemLayer.opacity=0;
temporalLayer.selectedItemLayer = temporalLayer.previousItemLayer;
if( temporalLayer.selectedItemLayer) temporalLayer.selectedItemLayer.opacity=1;
temporalLayer.previousItemLayer = index-2 >=0? new ItemLayer(temporalLayer.temporalItems[index-2],0):null;
if( temporalLayer.previousItemLayer) temporalLayer.previousItemLayer.opacity=0;
return tassign(state,{selectedItemLayer:tassign(state.selectedItemLayer,temporalLayer as ItemLayer)});
}
} else {
return state;
}
}
case mapActions.SELECTTEMPORAL:{
//todo implement
}
case mapActions.STARTSEARCH: { case mapActions.STARTSEARCH: {
let a = action as mapActions.StartSearch; let a = action as mapActions.StartSearch;
return tassign(state, { return tassign(state, {