diff --git a/.gitignore b/.gitignore index 274d3f2..b9c1a68 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ testem.log .DS_Store Thumbs.db projects/common/node_modules/ +projects/common-map/node_modules/ diff --git a/Jenkinsfile.develop b/Jenkinsfile.develop index b7a11f4..66f5310 100644 --- a/Jenkinsfile.develop +++ b/Jenkinsfile.develop @@ -12,13 +12,17 @@ pipeline { } stage('build'){ steps { - sh '''ng build common''' + sh '''ng build common + ng build common-map''' } } stage('npm publish'){ steps { sh '''cd dist/common npm version ${PACKAGE_VERSION} + npm publish + cd ../common-map + npm version ${PACKAGE_VERSION} npm publish''' } } diff --git a/angular.json b/angular.json index 0964473..c408c6a 100644 --- a/angular.json +++ b/angular.json @@ -142,7 +142,7 @@ "builder": "@angular-devkit/build-ng-packagr:build", "options": { "tsConfig": "projects/common/tsconfig.lib.json", - "project": "projects/common/ng-package.json" + "project": "projects/common/ng-package.json" } }, "test": { diff --git a/package-lock.json b/package-lock.json index d501b95..0f4f264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2793,6 +2793,11 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + }, "default-gateway": { "version": "2.7.2", "resolved": "https://repository.akkerweb.nl/repository/npm-group/default-gateway/-/default-gateway-2.7.2.tgz", @@ -6889,6 +6894,14 @@ } } }, + "ngrx-store-localstorage": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ngrx-store-localstorage/-/ngrx-store-localstorage-8.0.0.tgz", + "integrity": "sha512-3AHqw1AeXDXU/Hgxlh5fjP/srgGuP8BJR2uQ6ViGLeKwMEQDZuQfwd8zu9+bPZcvIxbAcxjWoGCVJXYEkBlxmg==", + "requires": { + "deepmerge": "^3.2.0" + } + }, "ngx-uploadx": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/ngx-uploadx/-/ngx-uploadx-3.1.3.tgz", diff --git a/package.json b/package.json index d92be6c..f9158d0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@ngrx/effects": "^7.2.0", "@ngrx/router-store": "^7.2.0", "@ngrx/store": "^7.2.0", + "ngrx-store-localstorage": "^8.0.0", "bootstrap": "^4.3.1", "core-js": "^2.5.4", "resumablejs": "^1.1.0", diff --git a/projects/common-map/karma.conf.js b/projects/common-map/karma.conf.js deleted file mode 100644 index 370c5f5..0000000 --- a/projects/common-map/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../../coverage/common-map'), - reports: ['html', 'lcovonly'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/projects/common-map/ng-package.json b/projects/common-map/ng-package.json index 5509b1c..1e00865 100644 --- a/projects/common-map/ng-package.json +++ b/projects/common-map/ng-package.json @@ -3,5 +3,8 @@ "dest": "../../dist/common-map", "lib": { "entryFile": "src/public-api.ts" - } + }, + "whitelistedNonPeerDependencies": [ + "." + ] } \ No newline at end of file diff --git a/projects/common-map/package-lock.json b/projects/common-map/package-lock.json new file mode 100644 index 0000000..1214e0c --- /dev/null +++ b/projects/common-map/package-lock.json @@ -0,0 +1,108 @@ +{ + "name": "@farmmaps/common-map", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@farmmaps/common": { + "version": "0.0.1-prerelease.43", + "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common/-/common-0.0.1-prerelease.43.tgz", + "integrity": "sha512-tV42SIpXAremVLolUEW0ESdYRdyllunbIZgHB3ObsqPJMrI0vgnkIr8LdtSaaYokAGXA3L8n6q3Zi78/hiBgQg==", + "requires": { + "angular-oauth2-oidc": "^5.0.2", + "ngx-uploadx": "^3.1.3", + "tslib": "^1.9.0" + } + }, + "angular-oauth2-oidc": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-5.0.2.tgz", + "integrity": "sha512-jtOv4IWEjSFfBHVE4seWGWT/ZfWJ95QJ1JaFhVVGJEF64ibGuPwV3ztwTOUl98QHi/Yg4PXXDAisb31JnIbxBw==", + "requires": { + "jsrsasign": "^8.0.12", + "tslib": "^1.9.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "jsrsasign": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", + "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" + }, + "ngx-openlayers": { + "version": "1.0.0-next.9", + "resolved": "https://registry.npmjs.org/ngx-openlayers/-/ngx-openlayers-1.0.0-next.9.tgz", + "integrity": "sha512-14UFxJX9oeOXtq+HJCJyXn0sBmYmCqj2AnFtetKk1FsDe8EUMFGIRju8UOFegCr2oEu5JsuRjALcfW7lCe+teg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-uploadx": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ngx-uploadx/-/ngx-uploadx-3.3.1.tgz", + "integrity": "sha512-2WsE2BgA1RlrCuJ2Arcl6vObfL+dJzZFdFnnOOjbsW0ib+MmC+6dEL7QXgTs+sRCOzs2wC+F1sQ27nMPjhvpjg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ol": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/ol/-/ol-5.3.3.tgz", + "integrity": "sha512-7eU4x8YMduNcED1D5wI+AMWDRe7/1HmGfsbV+kFFROI9RNABU/6n4osj6Q3trZbxxKnK2DSRIjIRGwRHT/Z+Ww==", + "requires": { + "pbf": "3.1.0", + "pixelworks": "1.1.0", + "rbush": "2.0.2" + } + }, + "pbf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.1.0.tgz", + "integrity": "sha512-/hYJmIsTmh7fMkHAWWXJ5b8IKLWdjdlAFb3IHkRBn1XUhIYBChVGfVwmHEAV3UfXTxsP/AKfYTXTS/dCPxJd5w==", + "requires": { + "ieee754": "^1.1.6", + "resolve-protobuf-schema": "^2.0.0" + } + }, + "pixelworks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pixelworks/-/pixelworks-1.1.0.tgz", + "integrity": "sha1-Hwla1I3Ki/ihyCWOAJIDGkTyLKU=" + }, + "protocol-buffers-schema": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", + "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" + }, + "quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" + }, + "rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "requires": { + "quickselect": "^1.0.1" + } + }, + "resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "requires": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } +} diff --git a/projects/common-map/package.json b/projects/common-map/package.json index 2426c54..b2f1f27 100644 --- a/projects/common-map/package.json +++ b/projects/common-map/package.json @@ -1,8 +1,22 @@ { - "name": "@farmmaps/common/map", + "name": "@farmmaps/common-map", "version": "0.0.1", + "publishConfig": { + "registry": "https://repository.akkerweb.nl/repository/npm-hosted/" + }, + "dependencies": { + "@farmmaps/common": "0.0.1-prerelease.43", + "ngx-openlayers": "1.0.0-next.9", + "ol": "^5.3.3" + }, "peerDependencies": { "@angular/common": "^7.2.0", - "@angular/core": "^7.2.0" + "@angular/core": "^7.2.0", + "ngrx-store-localstorage": "^8.0.0", + "@farmmaps/common": "^0.0.1", + "@ngrx/effects": "^7.2", + "@ngrx/router-store": "^7.2", + "@ngrx/store": "^7.2", + "tassign": "^1.0.0" } -} \ No newline at end of file +} diff --git a/projects/common-map/src/lib/_theme.scss b/projects/common-map/src/lib/_theme.scss new file mode 100644 index 0000000..ac853f9 --- /dev/null +++ b/projects/common-map/src/lib/_theme.scss @@ -0,0 +1,2 @@ +//$theme-colors: ( "primary": #a7ce39, "secondary": #ffc800 ); +//$theme-colors: ( "primary": #a7ce39); \ No newline at end of file diff --git a/projects/common-map/src/lib/actions/map.actions.ts b/projects/common-map/src/lib/actions/map.actions.ts new file mode 100644 index 0000000..7b24508 --- /dev/null +++ b/projects/common-map/src/lib/actions/map.actions.ts @@ -0,0 +1,241 @@ +import { Action } from '@ngrx/store'; + +import { IMapState,IItemLayer,IQueryState } from '../models'; +import { IItem } from '@farmmaps/common'; +import { Feature } from 'ol'; +import { Extent } from 'ol/extent'; + +export const SETSTATE = '[Map] SetState'; +export const SETMAPSTATE = '[Map] MapState'; +export const SETVIEWEXTENT = '[Map] SetViewExtent'; +export const INIT = '[Map] Init'; +export const SETPARENT = '[Map] SetParent'; +export const STARTSEARCH = '[Map] StartSearch'; +export const STARTSEARCHSUCCESS = '[Map] StartSearchSuccess'; +export const SELECTFEATURE = '[Map] SelectFeature'; +export const SELECTITEM = '[Map] SelectItem'; +export const SELECTITEMSUCCESS = '[Map] SelectItemSuccess'; +export const ADDFEATURESUCCESS = '[Map] AddFeatureSuccess'; +export const UPDATEFEATURESUCCESS = '[Map] UpdateFeatureSuccess'; +export const EXPANDSEARCH = '[Map] ExpandSearch'; +export const COLLAPSESEARCH = '[Map] CollapseSearch'; +export const TOGGLEMENU = '[Map] ToggleMenu'; +export const SETEXTENT = '[Map] SetExtent'; +export const SETQUERYSTATE = '[Map] SetQueryState'; +export const SETTIMESPAN = '[Map] SetTimeSpan'; +export const ADDLAYER = '[Map] AddLayer'; +export const SETVISIBILITY = '[Map] SetVisibility'; +export const SETOPACITY = '[Map] SetOpacity'; +export const SETLAYERINDEX = '[Map] SetLayerIndex'; +export const REMOVELAYER = '[Map] RemoveLayer'; +export const LOADBASELAYERS = '[Map] LoadLayers'; +export const LOADBASELAYERSSUCCESS = '[Map] LoadLayersSuccess'; +export const SELECTBASELAYER = '[Map] SelectBaseLayers'; +export const SELECTOVERLAYLAYER = '[Map] SelectOverlayLayers'; +export const ZOOMTOEXTENT = '[Map] ZoomToExtent'; +export const DOQUERY = '[Map] DoQuery'; + +export class SetState implements Action { + readonly type = SETSTATE; + + constructor(public mapState: IMapState,public queryState:IQueryState) { } +} + +export class SetMapState implements Action { + readonly type = SETMAPSTATE; + + constructor(public mapState: IMapState) { } +} + +export class SetViewExtent implements Action { + readonly type = SETVIEWEXTENT; + + constructor(public extent:number[]) { } +} + +export class Init implements Action { + readonly type = INIT; + + constructor() { } +} + +export class SetParent implements Action { + readonly type = SETPARENT; + + constructor(public parentCode:string) { } +} + +export class StartSearch implements Action { + readonly type = STARTSEARCH; + + constructor(public queryState: IQueryState) { } +} + +export class StartSearchSuccess implements Action { + readonly type = STARTSEARCHSUCCESS; + + constructor(public features: Array, public query:IQueryState) { } +} + +export class SelectFeature implements Action { + readonly type = SELECTFEATURE; + + constructor(public feature:Feature) { } +} + +export class SelectItem implements Action { + readonly type = SELECTITEM; + + constructor(public itemCode:string) { } +} + +export class SelectItemSuccess implements Action { + readonly type = SELECTITEMSUCCESS; + + constructor(public item: IItem) { } +} + +export class AddFeatureSuccess implements Action { + readonly type = ADDFEATURESUCCESS; + + constructor(public feature: Feature) { } +} + +export class UpdateFeatureSuccess implements Action { + readonly type = UPDATEFEATURESUCCESS; + + constructor(public feature: Feature) { } +} + +export class ExpandSearch implements Action { + readonly type = EXPANDSEARCH; + + constructor() { } +} + +export class CollapseSearch implements Action { + readonly type = COLLAPSESEARCH; + + constructor() { } +} + +export class ToggleMenu implements Action { + readonly type = TOGGLEMENU; + + constructor() { } +} + +export class SetExtent implements Action { + readonly type = SETEXTENT; + + constructor(public extent:number[]) { } +} + +export class SetQueryState implements Action { + readonly type = SETQUERYSTATE; + + constructor(public queryState: IQueryState) { } +} + +export class SetTimeSpan implements Action { + readonly type = SETTIMESPAN; + + constructor(public startDate: Date, public endDate: Date) { } +} + +export class AddLayer implements Action { + readonly type = ADDLAYER; + + constructor(public item:IItem,public layerIndex=-1) { } +} + +export class SetVisibility implements Action { + readonly type = SETVISIBILITY; + + constructor(public itemLayer:IItemLayer,public visibility:boolean) { } +} + +export class SetOpacity implements Action { + readonly type = SETOPACITY; + + constructor(public itemLayer: IItemLayer, public opacity: number) { } +} + +export class SetLayerIndex implements Action { + readonly type = SETLAYERINDEX; + + constructor(public layerIndex: number, public itemLayer: IItemLayer = null) { } +} + +export class RemoveLayer implements Action { + readonly type = REMOVELAYER; + + constructor(public itemLayer: IItemLayer) { } +} + +export class LoadBaseLayers implements Action { + readonly type = LOADBASELAYERS; + + constructor(public projection: string) { } +} + +export class LoadBaseLayersSuccess implements Action { + readonly type = LOADBASELAYERSSUCCESS; + + constructor(public items: IItem[] ) { } +} + +export class SelectBaseLayer implements Action { + readonly type = SELECTBASELAYER; + + constructor(public itemLayer: IItemLayer) { } +} + +export class SelectOverlayLayer implements Action { + readonly type = SELECTOVERLAYLAYER; + + constructor(public itemLayer: IItemLayer) { } +} + +export class ZoomToExtent implements Action { + readonly type = ZOOMTOEXTENT; + + constructor(public itemLayer: IItemLayer) { } +} + +export class DoQuery implements Action { + readonly type = DOQUERY; + + constructor(public query:IQueryState) { } +} + +export type Actions = SetMapState + | Init + | SetParent + | StartSearch + | StartSearchSuccess + | SelectFeature + | SelectItem + | SelectItemSuccess + | AddFeatureSuccess + | UpdateFeatureSuccess + | ExpandSearch + | CollapseSearch + | ToggleMenu + | SetExtent + | SetQueryState + | SetTimeSpan + | AddLayer + | RemoveLayer + | SetVisibility + | SetOpacity + | SetLayerIndex + | LoadBaseLayers + | LoadBaseLayersSuccess + | SelectBaseLayer + | SelectOverlayLayer + | ZoomToExtent + | SetState + | SetViewExtent + | DoQuery; + diff --git a/projects/common-map/src/lib/common-map-routing.module.ts b/projects/common-map/src/lib/common-map-routing.module.ts new file mode 100644 index 0000000..752818e --- /dev/null +++ b/projects/common-map/src/lib/common-map-routing.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { MapComponent } from './components/map/map.component'; +import { AuthGuard } from '@farmmaps/common'; + +const routes = [ + { + path: '', canActivateChild: [AuthGuard], children: [ + { + path: '', + component: MapComponent + } + ] + }, + { + path: ':xCenter/:yCenter/:zoom/:rotation/:baseLayer/:queryState', canActivateChild: [AuthGuard], children: [ + { + path: '', + component: MapComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class MapRoutingModule { } diff --git a/projects/common-map/src/lib/common-map.component.spec.ts b/projects/common-map/src/lib/common-map.component.spec.ts deleted file mode 100644 index f160c7a..0000000 --- a/projects/common-map/src/lib/common-map.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CommonMapComponent } from './common-map.component'; - -describe('CommonMapComponent', () => { - let component: CommonMapComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CommonMapComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CommonMapComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/common-map/src/lib/common-map.module.ts b/projects/common-map/src/lib/common-map.module.ts index 483318b..0b6f77d 100644 --- a/projects/common-map/src/lib/common-map.module.ts +++ b/projects/common-map/src/lib/common-map.module.ts @@ -1,10 +1,259 @@ import { NgModule } from '@angular/core'; -import { CommonMapComponent } from './common-map.component'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -@NgModule({ - declarations: [CommonMapComponent], +//external modules +import { AngularOpenlayersModule } from 'ngx-openlayers'; +import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; + +//common modules +import { AppCommonModule } from '@farmmaps/common'; + +import { MODULE_NAME } from './module-name'; +import * as mapReducer from './reducers/map.reducer'; +import * as mapEffects from './effects/map.effects'; + +// components +import { GpsLocation} from './components/aol/gps-location/gps-location.component'; +//import {Switch2D3DComponent } from './components/aol/switch2d3d/switch2d3d.component'; +import {FeatureListFeatureCropfieldComponent } from './components/feature-list-feature-cropfield/feature-list-feature-cropfield.component'; +import { FeatureListFeatureCroppingschemeComponent} from './components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component'; +import { ItemWidgetListComponent} from './components/item-widget-list/item-widget-list.component'; +import { AbstractItemListItemComponent, ItemListItemComponent, AbstractItemWidgetComponent } from './components/item-list-item/item-list-item.component'; +import { ItemListItemContainerComponent } from './components/item-list-item-container/item-list-item-container.component'; +import { AbstractItemListComponent,ItemListComponent} from './components/item-list/item-list.component'; +import { AbstractSelectedItemComponent, SelectedItemComponent } from './components/selected-item/selected-item.component'; +import { SelectedItemCropfieldComponent } from './components/selected-item-cropfield/selected-item-cropfield.component'; +import { SelectedItemGeotiffComponent } from './components/selected-item-geotiff/selected-item-geotiff.component'; +import {SelectedItemShapeComponent } from './components/selected-item-shape/selected-item-shape.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 {FeatureListFeatureContainerComponent } from './components/feature-list-feature-container/feature-list-feature-container.component'; +import { FeatureListCroppingschemeComponent } from './components/feature-list-croppingscheme/feature-list-croppingscheme.component'; +import {FeatureListCropfieldComponent } from './components/feature-list-cropfield/feature-list-cropfield.component'; +import {FeatureListContainerComponent } from './components/feature-list-container/feature-list-container.component'; +import { WidgetHostDirective} from './components/widget-host/widget-host.directive'; +import { FeatureListComponent,AbstractFeatureListComponent} from './components/feature-list/feature-list.component'; +import { FileDropTargetComponent } from './components/aol/file-drop-target/file-drop-target.component'; +import { ItemVectorSourceComponent } from './components/aol/item-vector-source/item-vector-source.component'; +import { ItemFeaturesSourceComponent } from './components/aol/item-features-source/item-features-source.component'; +import { ItemLayersComponent } from './components/aol/item-layers/item-layers.component'; +import { ZoomToExtentComponent } from './components/aol/zoom-to-extent/zoom-to-extent.component'; +import { RotationResetComponent } from './components/aol/rotation-reset/rotation-reset.component'; +import { LayerListComponent } from './components/aol/layer-list/layer-list.component'; +import { MetaDataModalComponent } from './components/meta-data-modal/meta-data-modal.component'; +import { SelectPeriodModalComponent } from './components/select-period-modal/select-period-modal.component'; +import { MapComponent } from './components/map/map.component'; +import { MapSearchComponent } from './components/map-search'; +import { MapRoutingModule } from './common-map-routing.module'; +import { LegendComponent } from './components/legend/legend.component'; +import { LayerVectorImageComponent } from './components/aol/layer-vector-image/layer-vector-image.component'; +import { StateSerializerService } from './services/state-serializer.service'; +import { GeolocationService } from './services/geolocation.service'; +import { localStorageSync } from 'ngrx-store-localstorage'; +import { ItemWidgetWeatherComponent } from './components/item-widget-weather/item-widget-weather.component'; +import { ItemListItemTemporalComponent } from './components/item-list-item-temporal/item-list-item-temporal.component'; +import { ItemListItemHeightComponent } from './components/item-list-item-height/item-list-item-height.component'; +import { ItemListItemTipstarComponent } from './components/item-list-item-tipstar/item-list-item-tipstar.component'; +import { ItemListItemWatBalComponent } from './components/item-list-item-watbal/item-list-item-watbal.component'; +import { WidgetStatusComponent } from './components/widget-status/widget-status.component'; +import { ItemListItemShadowComponent } from './components/item-list-item-shadow/item-list-item-shadow.component'; +import { ItemListItemBofekComponent } from './components/item-list-item-bofek/item-list-item-bofek.component'; + +export { + mapEffects, + mapReducer, + ZoomToExtentComponent, + ItemVectorSourceComponent, + ItemFeaturesSourceComponent, + ItemLayersComponent, + FileDropTargetComponent, + MapComponent, + MetaDataModalComponent, + RotationResetComponent, + MapSearchComponent, + SelectPeriodModalComponent, + LayerListComponent, + LegendComponent, + LayerVectorImageComponent, + FeatureListComponent, + WidgetHostDirective, + FeatureListContainerComponent, + FeatureListCroppingschemeComponent, + FeatureListCropfieldComponent, + FeatureListFeatureContainerComponent, + FeatureListFeatureComponent, + FeatureListFeatureCroppingschemeComponent, + FeatureListFeatureCropfieldComponent, + SelectedItemContainerComponent, + SelectedItemComponent, + SelectedItemCropfieldComponent, + SelectedItemGeotiffComponent, + SelectedItemShapeComponent, + ItemListItemComponent, + ItemListItemContainerComponent, + ItemListComponent, + ItemWidgetWeatherComponent, + ItemListItemTemporalComponent, + ItemWidgetListComponent, + ItemListItemHeightComponent, + ItemListItemTipstarComponent, + ItemListItemWatBalComponent, + ItemListItemShadowComponent, + WidgetStatusComponent, + GpsLocation, + ItemListItemBofekComponent, + AbstractFeatureListComponent, + AbstractFeatureListFeatureComponent, + AbstractSelectedItemComponent, + AbstractItemWidgetComponent, + AbstractItemListItemComponent, + AbstractItemListComponent, + StateSerializerService, + GeolocationService +} + +@NgModule({ imports: [ + CommonModule, + AngularOpenlayersModule, + MapRoutingModule, + StoreModule.forFeature(MODULE_NAME, mapReducer.reducer), + EffectsModule.forFeature([mapEffects.MapEffects]), + NgbModule, + FormsModule, + ReactiveFormsModule, + AppCommonModule ], - exports: [CommonMapComponent] + declarations: [ + ZoomToExtentComponent, + ItemVectorSourceComponent, + ItemFeaturesSourceComponent, + ItemLayersComponent, + FileDropTargetComponent, + MapComponent, + MetaDataModalComponent, + RotationResetComponent, + MapSearchComponent, + SelectPeriodModalComponent, + LayerListComponent, + LegendComponent, + LayerVectorImageComponent, + FeatureListComponent, + WidgetHostDirective, + FeatureListContainerComponent, + FeatureListCroppingschemeComponent, + FeatureListCropfieldComponent, + FeatureListFeatureContainerComponent, + FeatureListFeatureComponent, + FeatureListFeatureCroppingschemeComponent, + FeatureListFeatureCropfieldComponent, + SelectedItemContainerComponent, + SelectedItemComponent, + SelectedItemCropfieldComponent, + SelectedItemGeotiffComponent, + SelectedItemShapeComponent, + ItemListItemComponent, + ItemListItemContainerComponent, + ItemListComponent, + ItemWidgetWeatherComponent, + ItemListItemTemporalComponent, + ItemWidgetListComponent, + ItemListItemHeightComponent, + ItemListItemTipstarComponent, + ItemListItemWatBalComponent, + ItemListItemShadowComponent, + WidgetStatusComponent, + GpsLocation, + ItemListItemBofekComponent + // Switch2D3DComponent + ], + entryComponents: [ + // FeatureListComponent, + // FeatureListCroppingschemeComponent, + // FeatureListCropfieldComponent, + // FeatureListFeatureComponent, + // FeatureListFeatureCroppingschemeComponent, + // FeatureListFeatureCropfieldComponent, + // SelectedItemComponent, + // SelectedItemCropfieldComponent, + // SelectedItemGeotiffComponent, + // SelectedItemShapeComponent, + // ItemListComponent, + // ItemListItemComponent, + // ItemWidgetWeatherComponent, + // ItemListItemTemporalComponent, + // ItemListItemHeightComponent, + // ItemListItemTipstarComponent, + // ItemListItemWatBalComponent, + // ItemListItemShadowComponent, + // ItemListItemBofekComponent, + ], + // providers: [ + // StateSerializerService, + // GeolocationService, + // { provide: AbstractFeatureListComponent, useClass: FeatureListCroppingschemeComponent, multi: true }, + // { provide: AbstractFeatureListComponent, useClass: FeatureListCropfieldComponent, multi: true }, + // { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureComponent, multi: true }, + // { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureCroppingschemeComponent, multi: true }, + // { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureCropfieldComponent, multi: true }, + // { provide: AbstractSelectedItemComponent, useClass: SelectedItemComponent, multi: true }, + // { provide: AbstractSelectedItemComponent, useClass: SelectedItemCropfieldComponent, multi: true }, + // { provide: AbstractSelectedItemComponent, useClass: SelectedItemGeotiffComponent, multi: true }, + // { provide: AbstractSelectedItemComponent, useClass: SelectedItemShapeComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemComponent, multi: true }, + // { provide: AbstractItemWidgetComponent, useClass: ItemWidgetWeatherComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemTemporalComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemHeightComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemTipstarComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemWatBalComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemShadowComponent, multi: true }, + // { provide: AbstractItemListItemComponent, useClass: ItemListItemBofekComponent, multi: true }, + // { provide: AbstractItemListComponent, useClass: ItemListComponent, multi: true } + // ], + exports: [ + ItemVectorSourceComponent, + ItemFeaturesSourceComponent, + ItemLayersComponent, + FileDropTargetComponent, + MetaDataModalComponent, + MapComponent, + GpsLocation, + FeatureListFeatureComponent, + FeatureListFeatureCropfieldComponent, + FeatureListFeatureCroppingschemeComponent, + SelectedItemContainerComponent, + SelectedItemComponent, + SelectedItemCropfieldComponent, + SelectedItemGeotiffComponent, + SelectedItemShapeComponent, + ItemListItemComponent, + ItemListItemContainerComponent, + ItemListComponent, + ItemWidgetWeatherComponent, + ItemListItemTemporalComponent, + ItemWidgetListComponent, + ItemListItemHeightComponent, + ItemListItemTipstarComponent, + ItemListItemWatBalComponent, + ItemListItemShadowComponent, + WidgetStatusComponent, + ItemListItemBofekComponent, + RotationResetComponent, + MapSearchComponent, + SelectPeriodModalComponent, + LayerListComponent, + LegendComponent, + LayerVectorImageComponent, + FeatureListComponent, + WidgetHostDirective, + FeatureListContainerComponent, + FeatureListCroppingschemeComponent, + FeatureListCropfieldComponent, + FeatureListFeatureContainerComponent, + ZoomToExtentComponent + ] }) export class CommonMapModule { } diff --git a/projects/common-map/src/lib/common-map.service.spec.ts b/projects/common-map/src/lib/common-map.service.spec.ts deleted file mode 100644 index 5c0fa7c..0000000 --- a/projects/common-map/src/lib/common-map.service.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { CommonMapService } from './common-map.service'; - -describe('CommonMapService', () => { - beforeEach(() => TestBed.configureTestingModule({})); - - it('should be created', () => { - const service: CommonMapService = TestBed.get(CommonMapService); - expect(service).toBeTruthy(); - }); -}); diff --git a/projects/common-map/src/lib/common-map.service.ts b/projects/common-map/src/lib/common-map.service.ts deleted file mode 100644 index 2316538..0000000 --- a/projects/common-map/src/lib/common-map.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class CommonMapService { - - constructor() { } -} diff --git a/projects/common-map/src/lib/components/aol/file-drop-target/file-drop-target.component.ts b/projects/common-map/src/lib/components/aol/file-drop-target/file-drop-target.component.ts new file mode 100644 index 0000000..9912416 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/file-drop-target/file-drop-target.component.ts @@ -0,0 +1,73 @@ +import { Component, Input, OnDestroy, OnInit, EventEmitter, Output, Inject } from '@angular/core'; +import { MapComponent } from 'ngx-openlayers'; + +import * as proj from 'ol/proj'; +import {Point,Geometry} from 'ol/geom'; +import { GeoJSON } from 'ol/format'; +import { Feature } from 'ol'; + +export interface IDroppedFile { + files: any, + event: any, + geometry: any + parentCode: string; +} + +@Component({ + selector: 'file-drop-target', + template: '' +}) +export class FileDropTargetComponent implements OnInit, OnDestroy { + element: Element; + @Output() onFileDropped = new EventEmitter(); + @Input() parentCode: string; + @Input() features: Array; + + constructor(private map: MapComponent) { + } + + ngOnInit() { + this.element = this.map.instance.getViewport(); + let other = this; + this.element.addEventListener('drop', this.onDrop, false); + this.element.addEventListener('dragover', this.preventDefault, false); + this.element.addEventListener('dragenter', this.preventDefault, false); + } + + private onDrop = (event: DragEvent) => { + this.stopEvent(event); + let geojsonFormat = new GeoJSON(); + var parentCode = this.parentCode; + var coordinate = this.map.instance.getEventCoordinate(event); + //coordinate = proj.transform(coordinate, this.map.instance.getView().getProjection(), 'EPSG:4326'); + var geometry:Geometry = new Point(coordinate); + var hitFeatures = this.map.instance.getFeaturesAtPixel([event.pageX, event.pageY]); + var hitFeature = hitFeatures && hitFeatures.length > 0 ? hitFeatures[0] : null; + if (hitFeature) { + if (hitFeature.get("code")) { + parentCode = hitFeature.get("code"); + } + geometry = geojsonFormat.readGeometry(geojsonFormat.writeGeometry(geometry)); // create copy instead of reference + } + var projectedGeometry = geometry.transform(this.map.instance.getView().getProjection(), 'EPSG:4326'); + + if (event.dataTransfer && event.dataTransfer.files) { + this.onFileDropped.emit({ files: event.dataTransfer.files, event: event, geometry: JSON.parse(geojsonFormat.writeGeometry(projectedGeometry)),parentCode:parentCode}) + } + } + + private preventDefault(event) { + event.preventDefault(); + } + + private stopEvent(event) { + event.stopPropagation(); + event.preventDefault(); + } + + ngOnDestroy() { + this.element.removeEventListener('drop', this.onDrop); + this.element.removeEventListener('dragover', this.preventDefault); + this.element.removeEventListener('dragenter', this.preventDefault); + } +} diff --git a/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.html b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.html new file mode 100644 index 0000000..d06dab1 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.html @@ -0,0 +1,15 @@ +
+ + + + + + + + + + + + + +
diff --git a/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.scss b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.scss new file mode 100644 index 0000000..c199d1b --- /dev/null +++ b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.scss @@ -0,0 +1,36 @@ +@import "../../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + +.gps-location { + display:none; +} + +.center, .tolerance, .border { + stroke-width: 0; +} + +.tolerance { + fill: theme-color(); + fill-opacity:0.4; +} + +.border { + fill: white; +} + +.center { + fill: theme-color(); +} + +.stop1 { + stop-color: theme-color(); + stop-opacity:1; +} + +.stop2 { + stop-color: theme-color(); + stop-opacity: 0; +} + + diff --git a/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.ts b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.ts new file mode 100644 index 0000000..71f97df --- /dev/null +++ b/projects/common-map/src/lib/components/aol/gps-location/gps-location.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, Input, ViewChild, ElementRef, OnChanges, SimpleChanges } from '@angular/core'; +import { MapComponent } from 'ngx-openlayers'; +import Overlay from 'ol/Overlay'; +import { fromLonLat, toLonLat } from 'ol/proj'; + + +@Component({ + selector: 'gps-location', + templateUrl: './gps-location.component.html', + styleUrls: ['./gps-location.component.scss'] +}) +export class GpsLocation implements OnInit,OnChanges{ + + @Input() enable:boolean; + public instance: Overlay; + @Input() position: Position; + @Input() location: number[]=[0,0]; + @Input() locationTolerance: number = 0; + @Input() showHeading: boolean = false; + @Input() heading: number = 0; + @Input() headingTolerance: number = 0; + public locTolerancePixels: number = 0; + public path: string = ""; + public rotate: string = ""; + private resolution: number = 0; + @ViewChild('location') locationElement: ElementRef; + + constructor(private map: MapComponent) { + + } + + recalcLocationTolerance() { + this.locTolerancePixels = this.locationTolerance / this.resolution; + } + + ngOnInit() { + this.instance = new Overlay({ + stopEvent:false, + positioning: 'center-center', + position: fromLonLat( this.location), + element: this.locationElement.nativeElement + }); + var x = Math.tan(this.headingTolerance * Math.PI / 180)*40; + var y = Math.cos(this.headingTolerance * Math.PI / 180) * 40; + var y1 = Math.round(500 - y); + var x1 = Math.round(500 - x); + var y2 = Math.round(y1); + var x2 = Math.round(500 + x); + this.path = "M " + x2 + " " + y2 + " A 45 45,0,0,0, " + x1 + " " + y1 + " L 493 500 L 507 500 Z"; + this.rotate = "rotate(" + Math.round(this.heading) + " 500 500)"; + this.locTolerancePixels = this.locationTolerance; + this.map.instance.addOverlay(this.instance); + this.map.instance.getView().on('change:resolution', (evt) => { + this.resolution = evt.target.get('resolution'); + this.recalcLocationTolerance(); + }); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.position && this.instance) { + var p = changes.position.currentValue as Position; + this.instance.setPosition(fromLonLat([p.coords.longitude, p.coords.latitude])); + this.locationTolerance = p.coords.accuracy; + this.recalcLocationTolerance(); + this.heading = p.coords.heading; + } + } +} diff --git a/projects/common-map/src/lib/components/aol/item-features-source/item-features-source.component.ts b/projects/common-map/src/lib/components/aol/item-features-source/item-features-source.component.ts new file mode 100644 index 0000000..43a73d8 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/item-features-source/item-features-source.component.ts @@ -0,0 +1,90 @@ +import { Component, Host, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, forwardRef, Inject, InjectionToken } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { LayerVectorComponent, SourceVectorComponent, MapComponent, LayerImageComponent } from 'ngx-openlayers'; +import { LayerVectorImageComponent } from '../layer-vector-image/layer-vector-image.component'; +import { ItemService } from '@farmmaps/common'; +import { IItem } from '@farmmaps/common'; +import { ISelectedFeatures } from '../../../models'; + +import {Vector} from 'ol/source'; +import {Feature,Collection} from 'ol'; +import {Extent} from 'ol/extent'; +import * as style from 'ol/style'; +import * as proj from 'ol/proj'; +import Projection from 'ol/proj/Projection'; +import * as loadingstrategy from 'ol/loadingstrategy'; +import {GeoJSON} from 'ol/format'; + +@Component({ + selector: 'item-features-source', + template: ``, + providers: [ + { provide: SourceVectorComponent, useExisting: forwardRef(() => ItemFeaturesSourceComponent) } + ] +}) + +export class ItemFeaturesSourceComponent extends SourceVectorComponent implements OnInit, OnChanges { + instance:Vector; + features: Collection; + @Input() item: IItem; + @Input() layerIndex: number = 0; + constructor(@Host() private layer: LayerVectorImageComponent, private itemService: ItemService, @Host() private map: MapComponent) { + super(layer); + } + + private colorIndex = 0; + private styleCache = {}; + + ngOnInit() { + this.instance = new Vector({ + loader: (extent: Extent, resolution: number, projection: Projection) => { + let format = new GeoJSON(); + this.itemService.getItemFeatures(this.item.code, extent, projection.getCode(), this.layerIndex).subscribe((data) => { + var features = format.readFeatures(data); + // TODO 13/6/2019 The line below causes an endless loop. Action required? + this.instance.addFeatures(features); + // TODO 13/6/2019 Action required: check if feature collection hasn't been changed (Willem) + // var t = this.vectorSource.getFeatures(); + }); + }, + format: new GeoJSON(), + strategy: loadingstrategy.bbox + }); + this.features = new Collection(); + this.layer.instance + this.layer.instance.setStyle((feature) => { + var resolution = this.map.instance.getView().getResolution(); + var r = (2 / resolution) / 2; + var color = feature.get("color"); + return new style.Style( + { + fill: new style.Fill({ + color: color + }), + image: new style.Circle({ + fill: new style.Fill({ + color: color + }), + stroke: new style.Stroke({ + color: color, + width: 1.25 + }), + radius: r + }), + stroke: new style.Stroke({ + color: color, + width: 1.25 + }) + }); + }); + this.host.instance.setSource(this.instance); + } + + ngOnChanges(changes: SimpleChanges) { + if (this.instance) { + if (changes['layerIndex']) { + this.instance.clear(); + } + } + } +} diff --git a/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.js.map b/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.js.map new file mode 100644 index 0000000..3147cbe --- /dev/null +++ b/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"item-layers.component.js","sourceRoot":"","sources":["item-layers.component.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.ts b/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.ts new file mode 100644 index 0000000..ee23c41 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/item-layers/item-layers.component.ts @@ -0,0 +1,276 @@ +import { Component, Host, Input, Output, EventEmitter, Optional, QueryList, OnInit, AfterViewInit, OnChanges, SimpleChanges, SkipSelf, forwardRef, Inject, InjectionToken } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { LayerVectorComponent, LayerTileComponent, LayerGroupComponent, MapComponent } from 'ngx-openlayers'; +import { ItemService } from '@farmmaps/common'; +import { AppConfig } from '@farmmaps/common'; +import { IItemLayer, ILayerData, IRenderoutputTiles, IRenderoutputImage, IGradientstop, ILayer, IHistogram } from '../../../models'; + +import {Extent} from 'ol/extent'; +import Projection from 'ol/proj/Projection'; +import * as proj from 'ol/proj'; +import * as loadingstrategy from 'ol/loadingstrategy'; +import * as style from 'ol/style'; +import {Tile,Layer,Image} from 'ol/layer'; +import {XYZ,ImageStatic,OSM,BingMaps,TileWMS,TileArcGISRest} from 'ol/source'; +import {Vector as VectorSource} from 'ol/source'; +import { Vector as VectorLayer } from 'ol/layer'; +import VectorTileSource from 'ol/source/VectorTile'; +import VectorTileLayer from 'ol/layer/VectorTile'; +import {GeoJSON,MVT} from 'ol/format'; +import { from } from 'rxjs'; + +@Component({ + selector: 'item-layers', + template: ``, + providers: [ + { provide: LayerGroupComponent, useExisting: forwardRef(() => ItemLayersComponent) } + ] +}) + +export class ItemLayersComponent extends LayerGroupComponent implements OnChanges, OnInit { + @Input() itemLayers: IItemLayer[]; + @Input() itemLayer: IItemLayer; + private _apiEndPoint: string; + + constructor(private itemService: ItemService, @Host() private map: MapComponent, public appConfig: AppConfig) { + super(map); + this._apiEndPoint = "";//appConfig.getConfig("apiEndPoint"); + } + + private styleCache = {} + + componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + rgbaToHex(r, g, b,a) { + return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b) + this.componentToHex(a); + } + + getColorFromGradient(layer: ILayer, feature): style.Style { + var value = feature.get(layer.name); + var gradient: IGradientstop[] = layer.renderer.colorMap.gradient; + var histogram: IHistogram = layer.renderer.band.histogram; + var index = (value - histogram.min) / histogram.max; + var min = gradient[0]; + var max = gradient[gradient.length - 1]; + for (var n = 0; n < gradient.length; n++) { + var s = gradient[n]; + if (s.relativestop <= index && min.relativestop < s.relativestop && n < gradient.length - 1) min = s; + if (s.relativestop >= index && max.relativestop > s.relativestop && n > 0) max = s; + } + var i = index - min.relativestop; + var size = max.relativestop - min.relativestop; + var alpha = Math.round( min.color.alpha + ((max.color.alpha - min.color.alpha) * i / size)); + var red = Math.round(min.color.red + ((max.color.red - min.color.red) * i / size)); + var green = Math.round(min.color.green + ((max.color.green - min.color.green) * i / size)); + var blue = Math.round(min.color.blue + ((max.color.blue - min.color.blue) * i / size)); + + return new style.Style( + { + image: new style.Circle({ + fill: new style.Fill({ + color: this.rgbaToHex(red,green,blue,alpha) + }), + radius: 3 + }), + fill: new style.Fill({ + color: this.rgbaToHex(red, green, blue, alpha) + }), + stroke: new style.Stroke({ + color: this.rgbaToHex(red, green, blue, alpha), + width: 1.25 + }), + }); + } + + createLayer(itemLayer: IItemLayer): Layer { + var layer: Layer = null; + if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') { + let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); + 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 == "Tiles") { + var rt = l.rendering as IRenderoutputTiles; + let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); + layer = new Tile({ source: source }); + } + if (l && l.rendering && l.rendering.renderoutputType == "Image") { + var ri = l.rendering as IRenderoutputImage; + let projection = new Projection({ + code: 'image', + units: 'pixels', + extent: ri.extent + }); + let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/mapimage?v=${itemLayer.item.updated.getTime()}` }); + layer = new Image({ source: source }); + } + } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') { + var data = itemLayer.item.data; + var layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index; + var l = itemLayer.item.data.layers[layerIndex]; + if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") { + var rt = itemLayer.item.data.layers[layerIndex].rendering as IRenderoutputTiles; + layer = new VectorTileLayer({ + declutter: true, + source: new VectorTileSource({ + maxZoom: rt.maxzoom, + minZoom: rt.minzoom, + format: new MVT(), + url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${itemLayer.item.updated.getTime()}` + }), + style: (feature) => { + return this.getColorFromGradient(l, feature); + } + }) + } 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/${itemLayer.item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` + }) + }); + } else { + let __this = this; + let format = new GeoJSON(); + let source = new VectorSource({ + 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); + }); + } + }); + layer = new VectorLayer({ + source: source, + style: (feature) => { + var key = feature.get("color"); + if (!this.styleCache[key]) { + 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({ + color: color + }), + stroke: new style.Stroke({ + color: color, + width: 1.25 + }), + radius: 5 + }), + } + ) + } + return this.styleCache[key]; + } + }); + } + } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') { + let data = itemLayer.item.data as ILayerData; + 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) { + let geometry = new GeoJSON().readGeometry(itemLayer.item.geometry); + let extent = geometry ? proj.transformExtent(geometry.getExtent(), 'EPSG:4326', 'EPSG:3857') : null; + if (extent) layer.setExtent(extent); + } + + return layer; + } + + ngOnInit() { + super.ngOnInit(); + this.updateLayers(this.itemLayers); + } + + updateLayers(itemLayers: IItemLayer[]) { + if (itemLayers) { + 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) { + itemLayer.layer = layer; + } + olLayers.insertAt(index, layer); + } else if (index !== olIndex) { + // layer has moved inside the layers list + olLayers.removeAt(olIndex); + olLayers.insertAt(index, layer); + } + layer.setOpacity(itemLayer.opacity); + layer.setVisible(itemLayer.visible); + }); + // Remove the layers that have disapeared from childrenLayers + if (olLayers.getLength() > itemLayers.length) { + for (let i = itemLayers.length; i < olLayers.getLength(); i++) { + olLayers.removeAt(i); + } + } + } + } + + ngOnChanges(changes: SimpleChanges) { + if (this.instance) { + if (changes['itemLayers']) { + var itemLayers = changes['itemLayers'].currentValue as IItemLayer[]; + this.updateLayers(itemLayers); + } + if (changes['itemLayer']) { + var itemLayer = changes['itemLayer'].currentValue as IItemLayer; + if(itemLayer) { + this.updateLayers([itemLayer]); + } else { + this.updateLayers([]); + } + } + } + } +} diff --git a/projects/common-map/src/lib/components/aol/item-vector-source/item-vector-source.component.ts b/projects/common-map/src/lib/components/aol/item-vector-source/item-vector-source.component.ts new file mode 100644 index 0000000..290d6c0 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/item-vector-source/item-vector-source.component.ts @@ -0,0 +1,207 @@ +import { Component, Host, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, forwardRef, Inject, InjectionToken } from '@angular/core'; +import { LayerVectorComponent, SourceVectorComponent, MapComponent } from 'ngx-openlayers'; +import { ItemService,ItemTypeService,IItem, IItemType } from '@farmmaps/common'; + +import { Feature } from 'ol'; +import { Point } from 'ol/geom'; +import { MapBrowserEvent } from 'ol'; +import * as style from 'ol/style'; +import * as color from 'ol/color'; +import * as loadingstrategy from 'ol/loadingstrategy'; +import * as condition from 'ol/events/condition'; +import * as extent from 'ol/extent'; +import {Vector,Cluster} from 'ol/source'; +import {Layer} from 'ol/layer'; +import {GeoJSON} from 'ol/format'; +import {Select} from 'ol/interaction'; + +@Component({ + selector: 'item-source-vector', + template: ``, + providers: [ + { provide: SourceVectorComponent , useExisting: forwardRef(() => ItemVectorSourceComponent) } + ] +}) +export class ItemVectorSourceComponent extends SourceVectorComponent implements OnInit, OnChanges { + instance: Vector; + private _format: GeoJSON; + private _select: Select; + private _hoverSelect: Select; + private _iconScale: number = 0.05; + @Input() features: Array; + @Input() selectedFeature: Feature; + @Input() selectedItem: IItem; + @Output() onFeaturesSelected: EventEmitter = new EventEmitter(); + private styleCache = { + 'file': new style.Style({ + image: new style.Icon({ + anchor: [0.5, 1], + scale: 0.05, + src: this.getIconImageDataUrl("fa fa-file-o") + }), + stroke: new style.Stroke({ + color: 'red', + width: 1 + }), + fill: new style.Fill({ + color: 'rgba(0, 0, 255, 0.1)' + }), + geometry: (feature) => this.geometry(feature) + }), + 'selected': new style.Style({ + image: new style.Icon({ + anchor: [0.5, 1], + scale: 0.08, + src: this.getIconImageDataUrl(null) + }), + stroke: new style.Stroke({ + color: 'red', + width: 3 + }), + fill: new style.Fill({ + color: 'rgba(0, 0, 255, 0.1)' + }), + geometry: (feature) => this.geometry(feature) + }) + }; + + constructor(@Host() private layer: LayerVectorComponent, private itemService: ItemService, @Host() private map: MapComponent, private itemTypeService: ItemTypeService) { + super(layer); + this._format = new GeoJSON(); + } + + geometry(feature: Feature) { + let view = this.map.instance.getView(); + let resolution = view.getResolution(); + var geometry = feature.getGeometry(); + let e = geometry.getExtent(); + //var size = Math.max((e[2] - e[0]) / resolution, (e[3] - e[1]) / resolution); + if (resolution > 12) { + geometry = new Point(extent.getCenter(e)); + } + return geometry; + } + + getIconImageDataUrl(iconClass:string, backgroundColor: string = "#c80a6e",color:string = "#ffffff"): string { + var canvas = document.createElement('canvas'); + canvas.width = 365; + canvas.height = 560; + var ctx = canvas.getContext('2d'); + ctx.lineWidth = 6; + ctx.fillStyle = backgroundColor; + ctx.strokeStyle = "#000000"; + var path = new Path2D("m182.9 551.7c0 0.1 0.2 0.3 0.2 0.3s175.2-269 175.2-357.4c0-130.1-88.8-186.7-175.4-186.9-86.6 0.2-175.4 56.8-175.4 186.9 0 88.4 175.3 357.4 175.3 357.4z"); + ctx.fill(path) + + var iconCharacter = ""; + if (iconClass != null) { + var element = document.createElement("i"); + element.style.display = "none"; + element.className = iconClass; + document.body.appendChild(element); + iconCharacter = getComputedStyle(element, "::before").content.replace(/"/g, ''); + let iconFont = "200px " +getComputedStyle(element, "::before").fontFamily + document.body.removeChild(element); + ctx.strokeStyle = color; + ctx.fillStyle = color; + ctx.lineWidth = 15; + ctx.font = iconFont; + var ts = ctx.measureText(iconCharacter); + ctx.fillText(iconCharacter, 182.9 - (ts.width / 2), 250); + ctx.strokeText(iconCharacter, 182.9 - (ts.width / 2), 250); + } + + return canvas.toDataURL(); + } + + ngOnInit() { + this.strategy = loadingstrategy.bbox; + this.format = new GeoJSON(); + this._select = new Select({ + style: (feature) => { + return this.styleCache['selected']; + }, + hitTolerance: 10, + layers: [this.layer.instance as Layer] + }); + this._hoverSelect = new Select({ + style: (feature) => { + return this.styleCache['selected']; + }, + hitTolerance: 10, + condition: (e: MapBrowserEvent) => { + return e.type == 'pointermove'; + }, + layers: [this.layer.instance as Layer] + }); + this.map.instance.addInteraction(this._select); + this.map.instance.addInteraction(this._hoverSelect); + this._select.on('select', (e) => { + if (e.selected.length > 0 && e.selected[0]) { + this.onFeaturesSelected.emit(e.selected[0]); + } else { + this.onFeaturesSelected.emit(null); + } + }); + this.instance = new Vector(this); + this.host.instance.setSource(this.instance); + + this.host.instance.setStyle((feature) => { + var key = feature.get('itemType') + (this.selectedItem?"_I":""); + if (!this.styleCache[key]) { + if (this.itemTypeService.itemTypes[key]) { + let itemType = this.itemTypeService.itemTypes[key]; + let fillColor = color.asArray(itemType.iconColor); + fillColor[3] = this.selectedItem?0:0.5; + this.styleCache[key] = new style.Style({ + image: itemType.icon ? new style.Icon({ + anchor: [0.5, 1], + scale: 0.05, + src: this.getIconImageDataUrl(itemType.icon) + }):null, + stroke: new style.Stroke({ + color: 'red', + width: 1 + }), + fill: new style.Fill({ + color: fillColor + }), + geometry: (feature) => this.geometry(feature) + }); + } else { + key = 'file'; + } + } + var styleEntry = this.styleCache[key]; + return styleEntry; + }); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes["features"] && this.instance) { + this.instance.clear(true); + this._select.getFeatures().clear(); + this.instance.addFeatures(changes["features"].currentValue); + } + + if (changes["selectedFeature"] && this.instance) { + var features = this._select.getFeatures(); + var feature = changes["selectedFeature"].currentValue + //this.instance.clear(false); + //this.instance.addFeatures(features.getArray()); + features.clear(); + if (feature) { + //this.instance.removeFeature(feature); + features.push(feature) + } + } + if (changes["selectedItem"] && this.instance) { + var item = changes["selectedItem"].currentValue + if (item) { + this.map.instance.removeInteraction(this._hoverSelect); + } else { + this.map.instance.addInteraction(this._hoverSelect); + } + } + } +} diff --git a/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.html b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.html new file mode 100644 index 0000000..50d9448 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.html @@ -0,0 +1,29 @@ +
+
+
+
{{itemLayer.item.name}}
+
+ + + 25% + 50% + 75% + 100% + + + + +
+
+
+ +
+
+
+
+
+ +
+
No layers
+
+
diff --git a/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.scss b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.scss new file mode 100644 index 0000000..5f8de97 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.scss @@ -0,0 +1,18 @@ +.layerlist ul { + list-style:none; + padding:0; +} + +.active span.btn.btn-lnk.p-0.border-0 { + color:white; +} + +.btn-sm { + padding:0.25rem 0.3rem; +} + +.legend { + margin-top:0.5rem; + color:black; + padding:0.25rem; +} diff --git a/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.ts b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.ts new file mode 100644 index 0000000..23cc860 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/layer-list/layer-list.component.ts @@ -0,0 +1,64 @@ +import { Component,Input,Output,EventEmitter } from '@angular/core'; +import { IItemLayer } from '../../../models'; + +@Component({ + selector: 'layer-list', + templateUrl: './layer-list.component.html', + styleUrls: ['./layer-list.component.scss'] +}) + +export class LayerListComponent { + @Input() itemLayers: IItemLayer[]; + @Input() baseLayers: boolean = false; + @Output() onToggleVisibility = new EventEmitter(); + @Output() onSetOpacity = new EventEmitter<{layer: IItemLayer,opacity:number }>(); + @Output() onDelete = new EventEmitter(); + @Output() onZoomToExtent = new EventEmitter(); + @Output() onSelectLayer = new EventEmitter(); + @Input() selectedLayer: IItemLayer; + + constructor( ) { + } + + handleDelete(event:MouseEvent, item:IItemLayer) { + this.onDelete.emit(item); + event.preventDefault(); + } + + handleToggleVisibility(event:MouseEvent, item: IItemLayer) { + this.onToggleVisibility.emit(item); + item.legendVisible = item.visible && item.legendVisible; + event.preventDefault(); + } + + handleSetOpacity(event: MouseEvent, layer: IItemLayer,opacity:number) { + this.onSetOpacity.emit({ layer,opacity }); + event.preventDefault(); + } + + handleZoomToExtent(event: MouseEvent, item: IItemLayer) { + this.onZoomToExtent.emit(item); + event.preventDefault(); + } + + handleSelectLayer(event: MouseEvent, item: IItemLayer) { + this.onSelectLayer.emit(item); + event.preventDefault(); + } + + firstLayer(item: IItemLayer): any { + if (item && item.item && item.item.data && item.item.data.layers && item.item.data.layers.length > 0) return item.item.data.layers[0]; + return null; + } + + toggleLegend(event: MouseEvent, lg: boolean) { + event.preventDefault(); + return !lg; + } + + handleLegendClick(event: MouseEvent, item: IItemLayer) { + this.onSelectLayer.emit(item); + this.onZoomToExtent.emit(item); + event.preventDefault(); + } +} diff --git a/projects/common-map/src/lib/components/aol/layer-vector-image/layer-vector-image.component.ts b/projects/common-map/src/lib/components/aol/layer-vector-image/layer-vector-image.component.ts new file mode 100644 index 0000000..b777bd6 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/layer-vector-image/layer-vector-image.component.ts @@ -0,0 +1,31 @@ +import { Component, OnDestroy, OnInit, Input, Optional, OnChanges, SimpleChanges } from '@angular/core'; +import { Vector } from 'ol/layer'; +import { Style } from 'ol/style'; +import { StyleFunction } from 'ol/style/Style'; +import { LayerVectorComponent, LayerGroupComponent, MapComponent } from 'ngx-openlayers'; +import { RenderType } from 'ol/layer/Vector'; + +@Component({ + selector: 'aol-layer-vector-image', + template: ` + + `, +}) +export class LayerVectorImageComponent extends LayerVectorComponent implements OnInit, OnDestroy, OnChanges { + public source: Vector; + + @Input() + renderMode: RenderType | string = "image"; + + constructor(map: MapComponent) { + super(map); + } + + ngOnInit() { + super.ngOnInit(); + } + + ngOnChanges(changes: SimpleChanges) { + super.ngOnChanges(changes); + } +} diff --git a/projects/common-map/src/lib/components/aol/rotation-reset/rotation-reset.component.ts b/projects/common-map/src/lib/components/aol/rotation-reset/rotation-reset.component.ts new file mode 100644 index 0000000..366f6bc --- /dev/null +++ b/projects/common-map/src/lib/components/aol/rotation-reset/rotation-reset.component.ts @@ -0,0 +1,53 @@ +import { Component, Host, Input, OnInit, ChangeDetectorRef } from '@angular/core'; +import { ViewComponent, MapComponent } from 'ngx-openlayers'; + +import {View} from 'ol'; + + + +@Component({ + selector: 'rotation-reset', + template: `
`, + styles: [`.compass { + width:2.5em; + height:2.5em; + background-color: white; + background-size: contain; + background-image: url(data:image/png;base64,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); + opacity: 1; + } + .compass-n { + background-image: url(data:image/png;base64,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); + transition: opacity 1s ease-out; + transition-delay: 2s; + opacity:0; + } +`] +}) +export class RotationResetComponent implements OnInit { + view: View; + + public Rotation() { + let rotation = this.view ? this.view.getRotation() : 0; + return `rotate(${rotation}rad)`; + } + + public IsNorth() { + return this.view ? this.view.getRotation() == 0 : true; + } + + ngOnInit(): void { + this.view = this.map.instance.getView(); + this.view.on('change:rotation', () => { + this.changeDetectorRef$.detectChanges(); + }); + } + + constructor( @Host() private map: MapComponent, private changeDetectorRef$: ChangeDetectorRef ) { + } + + handleClick(event:Event) { + this.view.animate({ rotation: 0 }); + event.preventDefault(); + } +} diff --git a/projects/common-map/src/lib/components/aol/switch2d3d/switch2d3d.component.ts b/projects/common-map/src/lib/components/aol/switch2d3d/switch2d3d.component.ts new file mode 100644 index 0000000..a041af8 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/switch2d3d/switch2d3d.component.ts @@ -0,0 +1,40 @@ +// import { Component, OnInit,Input } from '@angular/core'; +//import { MapComponent } from 'ngx-openlayers'; +//import OLCesium from 'olcs/OLCesium.js'; + + +// @Component({ +// selector: 'switch2d3d', +// template: '
{{label}}
', +// styles: [`.twotreed { +// width:2.5em; +// height:2.5em; +// background-color: white; +// text-align:center; +// line-height:2.5em; +// font-weight:bold; +// cursor:default;}`] + +// }) +// export class Switch2D3DComponent { + +// @Input() enable:boolean; +// public label: string = "3D"; +// private ol3d: OLCesium; + + +// constructor(private map: MapComponent) { + +// } + +// ngOnInit() { +// this.ol3d = new OLCesium({ map: this.map.instance }); // ol2dMap is the ol.Map instance +// } + +// handleClick(event) { +// this.enable = !this.enable; +// if (this.enable) +// this.ol3d.setEnabled(this.enable); +// this.label = this.enable?"2D":"3D"; +// } +// } diff --git a/projects/common-map/src/lib/components/aol/zoom-to-extent/zoom-to-extent.component.ts b/projects/common-map/src/lib/components/aol/zoom-to-extent/zoom-to-extent.component.ts new file mode 100644 index 0000000..06bb0c8 --- /dev/null +++ b/projects/common-map/src/lib/components/aol/zoom-to-extent/zoom-to-extent.component.ts @@ -0,0 +1,39 @@ +import { Component, Host, Input, OnInit, OnChanges, SimpleChanges, forwardRef } from '@angular/core'; +import { ViewComponent, MapComponent } from 'ngx-openlayers'; + + +@Component({ + selector: 'zoom-to-extent', + template: `` +}) +export class ZoomToExtentComponent implements OnChanges { + view: ViewComponent; + map: MapComponent; + @Input() extent: number[]; + @Input() animate: boolean = false; + + constructor(@Host() view: ViewComponent, @Host() map: MapComponent) { + this.view = view; + this.map = map; + } + + ngOnChanges(changes: SimpleChanges) { + if (this.extent) { + var options = { padding: [0, 0, 0, 0],minResolution:1 }; + let size = this.map.instance.getSize(); + let rem = parseFloat(getComputedStyle(document.documentElement).fontSize); + let threshold = 44 * rem; + var left = 1 * rem; + var right = 1 * rem; + var bottom = Math.round(size[1] / 2); + var top = 1 * rem; + if (size[0] > threshold) { + bottom = 1 * rem; + left = 23 * rem; + } + options.padding = [top, right, bottom, left]; + if (this.animate) options["duration"] = 2000; + this.view.instance.fit(this.extent, options); + } + } +} diff --git a/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.html b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.html new file mode 100644 index 0000000..37a17dc --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.scss b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.scss new file mode 100644 index 0000000..b250578 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.scss @@ -0,0 +1,19 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + + +.row { + border-bottom: 1px solid gray('500'); + user-select: none; +} + +.row:hover { + background-color: gray('100'); +} + +@media screen and (min-width: 44rem) { + .feature-list-container { + margin-top: 4rem; + } +} diff --git a/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.ts b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.ts new file mode 100644 index 0000000..e0602cb --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-container/feature-list-container.component.ts @@ -0,0 +1,59 @@ +import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleChanges, ComponentFactory, Inject, Type} from '@angular/core'; +import { Feature } from 'ol'; +import { FeatureListComponent,AbstractFeatureListComponent } from '../feature-list/feature-list.component'; +import { WidgetHostDirective } from '../widget-host/widget-host.directive'; +import {IQueryState } from '../../models/query.state'; +import * as mapReducers from '../../reducers/map.reducer'; +import * as mapActions from '../../actions/map.actions'; +import { Store } from '@ngrx/store'; + + +@Component({ + selector: 'feature-list-container', + templateUrl: './feature-list-container.component.html', + styleUrls: ['./feature-list-container.component.scss'] +}) +export class FeatureListContainerComponent { + + constructor(private store: Store,private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractFeatureListComponent) public featureLists: AbstractFeatureListComponent[] ) { + } + + @Input() features: Array + @Input() queryState: IQueryState; + + @ViewChild(WidgetHostDirective) widgetHost: WidgetHostDirective; + + loadComponent(queryState:IQueryState) { + var componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(FeatureListComponent); // default + var selected = -1; + for (var i = 0; i < this.featureLists.length; i++) { + if (this.featureLists[i]['forItemType'] == queryState.itemType && this.featureLists[i]['forChild'] && queryState.parentCode && queryState.parentCode != "") { + selected = i; + break; + } else if (this.featureLists[i]['forItemType'] == queryState.itemType) { + selected = i; + break; + } + } + if (selected >= 0) { + componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[i]['constructor'] as any); + if (this.featureLists[selected]['collapseSearch'] === true) { + this.store.dispatch(new mapActions.CollapseSearch()); + } + } + const viewContainerRef = this.widgetHost.viewContainerRef; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).features = this.features; + (componentRef.instance).queryState = this.queryState; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes["features"] && changes["features"].currentValue) { + if (this.queryState) { + this.loadComponent(this.queryState); + } + } + } +} diff --git a/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.html b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.html new file mode 100644 index 0000000..aa39bec --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.html @@ -0,0 +1,14 @@ +
+
+
+ +

Farm

+

{{schemeItem.name}}

+
+
+ +
+
+
+
+
diff --git a/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.scss b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.scss new file mode 100644 index 0000000..dce6488 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.scss @@ -0,0 +1,23 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +feature-list-feature-container { + width:100%; + pointer-events:none; +} + +.row { + border-bottom: 1px solid gray('500'); + user-select: none; + padding-left:1.5rem; +} + +.row:hover { + background-color: gray('100'); +} + +.cropfields { + border-top: 1px solid gray('500'); + margin-left: -1.5rem; + margin-right: -1.5rem; +} diff --git a/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.ts b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.ts new file mode 100644 index 0000000..d79208b --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-cropfield/feature-list-cropfield.component.ts @@ -0,0 +1,30 @@ +import { Component, Injectable,AfterViewInit, OnInit} from '@angular/core'; +import { Location } from '@angular/common'; +import { AbstractFeatureListComponent } from '../feature-list/feature-list.component'; +import {ForItemType } from '../for-item/for-itemtype.decorator'; +import {ForChild } from '../for-item/for-child.decorator'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem,ItemService } from '@farmmaps/common'; +import { Observable } from 'rxjs'; + +@ForChild() +@ForItemType("vnd.farmmaps.itemtype.cropfield") +@Injectable() +@Component({ + selector: 'feature-list-cropfield', + templateUrl: './feature-list-cropfield.component.html', + styleUrls: ['./feature-list-cropfield.component.scss'] +}) +export class FeatureListCropfieldComponent extends AbstractFeatureListComponent implements OnInit { + + constructor(store: Store, itemTypeService: ItemTypeService, location: Location, private itemService: ItemService) { + super(store, itemTypeService,location); + } + + public schemeItem: Observable + + ngOnInit() { + this.schemeItem = this.itemService.getItem(this.queryState.parentCode); + } +} diff --git a/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.html b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.html new file mode 100644 index 0000000..4abcfce --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.html @@ -0,0 +1,13 @@ +
+
+
+ +

Farms

+
+
+ +
+
+
+
+
diff --git a/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.scss b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.scss new file mode 100644 index 0000000..3909fb7 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.scss @@ -0,0 +1,23 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +feature-list-feature-container { + width:100%; + pointer-events:none; +} + +.row { + border-bottom: 1px solid gray('500'); + user-select: none; + padding-left:1.5rem; +} + +.row:hover { + background-color: gray('100'); +} + +.farms { + border-top: 1px solid gray('500'); + margin-left: -1.5rem; + margin-right: -1.5rem; +} diff --git a/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.ts b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.ts new file mode 100644 index 0000000..775a96a --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-croppingscheme/feature-list-croppingscheme.component.ts @@ -0,0 +1,29 @@ +import { Component, Injectable } from '@angular/core'; +import { Location } from '@angular/common'; +import { AbstractFeatureListComponent } from '../feature-list/feature-list.component'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService } from '@farmmaps/common'; +import * as mapActions from '../../actions/map.actions'; +import { tassign } from 'tassign'; +import { Router } from '@angular/router'; + +@ForItemType("vnd.farmmaps.itemtype.croppingscheme") +@Injectable() +@Component({ + selector: 'feature-list-croppingscheme', + templateUrl: './feature-list-croppingscheme.component.html', + styleUrls: ['./feature-list-croppingscheme.component.scss'] +}) +export class FeatureListCroppingschemeComponent extends AbstractFeatureListComponent { + + constructor(store: Store, itemTypeService: ItemTypeService, location: Location, private router: Router) { + super(store, itemTypeService, location); + } + + handleFeatureClick(feature) { + var queryState = tassign(mapReducers.initialState.queryState, { parentCode: feature.get('code'), itemType: "vnd.farmmaps.itemtype.cropfield" }); + this.store.dispatch(new mapActions.DoQuery(queryState)); + } +} diff --git a/projects/common-map/src/lib/components/feature-list-feature-container/feature-list-feature-container.component.ts b/projects/common-map/src/lib/components/feature-list-feature-container/feature-list-feature-container.component.ts new file mode 100644 index 0000000..c0f870e --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-container/feature-list-feature-container.component.ts @@ -0,0 +1,43 @@ +import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleChanges, ComponentFactory, Inject, Type} from '@angular/core'; +import { Feature } from 'ol'; +import { AbstractFeatureListFeatureComponent,FeatureListFeatureComponent } from '../feature-list-feature/feature-list-feature.component'; +import { WidgetHostDirective } from '../widget-host/widget-host.directive'; + + +@Component({ + selector: 'feature-list-feature-container', + template: ` +
+ +
+ ` +}) +export class FeatureListFeatureContainerComponent { + + constructor(private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractFeatureListFeatureComponent) public featureLists: AbstractFeatureListFeatureComponent[] ) { + } + + @Input() feature: Feature; + + @ViewChild(WidgetHostDirective) widgetHost: WidgetHostDirective; + + loadComponent() { + var componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(FeatureListFeatureComponent); // default + for (var i = 0; i < this.featureLists.length; i++) { + if (this.featureLists[i]['forItemType'] == this.feature.get("itemType")) { + componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[i]['constructor'] as any); + } + } + const viewContainerRef = this.widgetHost.viewContainerRef; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).feature = this.feature; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes["feature"] && changes["feature"].currentValue) { + this.loadComponent(); + } + } +} diff --git a/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.html b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.html new file mode 100644 index 0000000..2892358 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.html @@ -0,0 +1,6 @@ +
+
+

{{feature.get('name')}}

+
{{feature.get('datadate')|date}} - {{feature.get('dataenddate')|date}}
+
+
diff --git a/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.scss b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.scss new file mode 100644 index 0000000..95f4b07 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.scss @@ -0,0 +1,30 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + + +.card-title { + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.thumbnail > img { + width: 100%; + height: auto; +} + +.thumbnail > div { + width: 100%; + font-size: 2rem; + text-align: center; + min-height: 3rem; + color: white; + padding-top: 0.5rem; +} + +.col { + overflow: hidden; +} + diff --git a/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.ts b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.ts new file mode 100644 index 0000000..8572b60 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-cropfield/feature-list-feature-cropfield.component.ts @@ -0,0 +1,22 @@ +import { Component, Input, Injectable} from '@angular/core'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService } from '@farmmaps/common'; +import { AbstractFeatureListFeatureComponent } from '../feature-list-feature/feature-list-feature.component'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; + + +@ForItemType("vnd.farmmaps.itemtype.cropfield") +@Injectable() +@Component({ + selector: 'feature-list-feature-cropfield', + templateUrl: './feature-list-feature-cropfield.component.html', + styleUrls: ['./feature-list-feature-cropfield.component.scss'] +}) +export class FeatureListFeatureCropfieldComponent extends AbstractFeatureListFeatureComponent { + + constructor(store: Store, itemTypeService: ItemTypeService) { + super(store, itemTypeService); + } +} diff --git a/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.html b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.html new file mode 100644 index 0000000..c817671 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.html @@ -0,0 +1,6 @@ +
+
+

{{feature.get('name')}}

+
{{feature.get('datadate')|date:'shortDate'}}
+
+
diff --git a/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.scss b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.scss new file mode 100644 index 0000000..95f4b07 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.scss @@ -0,0 +1,30 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + + +.card-title { + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.thumbnail > img { + width: 100%; + height: auto; +} + +.thumbnail > div { + width: 100%; + font-size: 2rem; + text-align: center; + min-height: 3rem; + color: white; + padding-top: 0.5rem; +} + +.col { + overflow: hidden; +} + diff --git a/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.ts b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.ts new file mode 100644 index 0000000..74c5038 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component.ts @@ -0,0 +1,22 @@ +import { Component, Input, Injectable} from '@angular/core'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService } from '@farmmaps/common'; +import { AbstractFeatureListFeatureComponent } from '../feature-list-feature/feature-list-feature.component'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; + + +@ForItemType("vnd.farmmaps.itemtype.croppingscheme") +@Injectable() +@Component({ + selector: 'feature-list-feature-croppingscheme', + templateUrl: './feature-list-feature-croppingscheme.component.html', + styleUrls: ['./feature-list-feature-croppingscheme.component.scss'] +}) +export class FeatureListFeatureCroppingschemeComponent extends AbstractFeatureListFeatureComponent { + + constructor(store: Store, itemTypeService: ItemTypeService) { + super(store, itemTypeService); + } +} diff --git a/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.html b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.html new file mode 100644 index 0000000..c5d7fc1 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.html @@ -0,0 +1,12 @@ +
+
+ +
+ +
+
+
+

 {{feature.get('name')}}

+
{{feature.get('datadate')|date:'shortDate'}}
+
+
diff --git a/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.scss b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.scss new file mode 100644 index 0000000..95f4b07 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.scss @@ -0,0 +1,30 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + + +.card-title { + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.thumbnail > img { + width: 100%; + height: auto; +} + +.thumbnail > div { + width: 100%; + font-size: 2rem; + text-align: center; + min-height: 3rem; + color: white; + padding-top: 0.5rem; +} + +.col { + overflow: hidden; +} + diff --git a/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.ts b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.ts new file mode 100644 index 0000000..a1e8c4f --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list-feature/feature-list-feature.component.ts @@ -0,0 +1,28 @@ +import { Component, Input, Injectable} from '@angular/core'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService } from '@farmmaps/common'; + + + +@Injectable() +export abstract class AbstractFeatureListFeatureComponent { + @Input() feature: Feature + + constructor(public store: Store, public itemTypeService: ItemTypeService) { + } +} + +@Injectable() +@Component({ + selector: 'feature-list-feature', + templateUrl: './feature-list-feature.component.html', + styleUrls: ['./feature-list-feature.component.scss'] +}) +export class FeatureListFeatureComponent extends AbstractFeatureListFeatureComponent { + + constructor(store: Store, itemTypeService: ItemTypeService) { + super(store, itemTypeService); + } +} diff --git a/projects/common-map/src/lib/components/feature-list/feature-list.component.html b/projects/common-map/src/lib/components/feature-list/feature-list.component.html new file mode 100644 index 0000000..f68d527 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list/feature-list.component.html @@ -0,0 +1,6 @@ +
+ Go back +
+ +
+
diff --git a/projects/common-map/src/lib/components/feature-list/feature-list.component.scss b/projects/common-map/src/lib/components/feature-list/feature-list.component.scss new file mode 100644 index 0000000..8f36540 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list/feature-list.component.scss @@ -0,0 +1,16 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +feature-list-feature-container { + width: 100%; + pointer-events: none; +} + +.row { + border-bottom: 1px solid gray('500'); + user-select: none; +} + +.row:hover { + background-color: gray('100'); +} diff --git a/projects/common-map/src/lib/components/feature-list/feature-list.component.ts b/projects/common-map/src/lib/components/feature-list/feature-list.component.ts new file mode 100644 index 0000000..2ae3886 --- /dev/null +++ b/projects/common-map/src/lib/components/feature-list/feature-list.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, Injectable } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService, IItem, Item } from '@farmmaps/common'; +import * as mapActions from '../../actions/map.actions'; +import { Observable, from } from 'rxjs'; +import { withLatestFrom } from 'rxjs/operators'; +import { tassign } from 'tassign'; +import { IQueryState } from '../../models'; + + +@Injectable() +export abstract class AbstractFeatureListComponent { + @Input() features: Array; + @Input() queryState: IQueryState; + constructor(public store: Store, public itemTypeService: ItemTypeService, private location: Location) { + } + + handleFeatureClick(feature:Feature) { + var newQuery: any = tassign(mapReducers.initialState.query); + newQuery.itemCode = feature.get('code'); + this.store.dispatch(new mapActions.DoQuery(newQuery)); + } + + handleFeatureMouseEnter(feature) { + this.store.dispatch(new mapActions.SelectFeature(feature)); + } + + handleFeatureMouseLeave(feature) { + this.store.dispatch(new mapActions.SelectFeature(null)); + } + + handleBackClick(event: MouseEvent) { + event.preventDefault(); + this.location.back(); + } +} + +@Injectable() +@Component({ + selector: 'feature-list', + templateUrl: './feature-list.component.html', + styleUrls: ['./feature-list.component.scss'] +}) +export class FeatureListComponent extends AbstractFeatureListComponent { + + constructor(store: Store, itemTypeService: ItemTypeService,location:Location) { + super(store, itemTypeService,location); + } +} diff --git a/projects/common-map/src/lib/components/for-item/for-child.decorator.ts b/projects/common-map/src/lib/components/for-item/for-child.decorator.ts new file mode 100644 index 0000000..d150ff1 --- /dev/null +++ b/projects/common-map/src/lib/components/for-item/for-child.decorator.ts @@ -0,0 +1,5 @@ +export function ForChild() { + return function (constructor:Function) { + constructor.prototype.forChild = true; + }; +} diff --git a/projects/common-map/src/lib/components/for-item/for-itemtype.decorator.ts b/projects/common-map/src/lib/components/for-item/for-itemtype.decorator.ts new file mode 100644 index 0000000..8887239 --- /dev/null +++ b/projects/common-map/src/lib/components/for-item/for-itemtype.decorator.ts @@ -0,0 +1,5 @@ +export function ForItemType(itemType: string) { + return function (constructor:Function) { + constructor.prototype.forItemType = itemType; + }; +} diff --git a/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.js.map b/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.js.map new file mode 100644 index 0000000..dced9bf --- /dev/null +++ b/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"for-sourcetask.decorator.js","sourceRoot":"","sources":["for-sourcetask.decorator.ts"],"names":[],"mappings":";;AAAA,SAAgB,aAAa,CAAC,UAAkB;IAC9C,OAAO,UAAU,WAAoB;QACnC,WAAW,CAAC,SAAS,CAAC,aAAa,GAAG,UAAU,CAAC;IACnD,CAAC,CAAC;AACJ,CAAC;AAJD,sCAIC"} \ No newline at end of file diff --git a/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.ts b/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.ts new file mode 100644 index 0000000..03d4664 --- /dev/null +++ b/projects/common-map/src/lib/components/for-item/for-sourcetask.decorator.ts @@ -0,0 +1,5 @@ +export function ForSourceTask(sourceTask: string) { + return function (constructor:Function) { + constructor.prototype.forSourceTask = sourceTask; + }; +} diff --git a/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.css b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.css new file mode 100644 index 0000000..691958b --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.css @@ -0,0 +1,24 @@ +.head { + color: grey; + font-weight: bold; + /*border-bottom:1px solid black;*/ +} + +.body { + display: block; +} + +.widget { + display: block; + height: 100%; + width: 100%; +} + +.unit { + font-size: 0.8rem; + color: gray; +} + +.dateformat { + font-size: 0.8rem; +} diff --git a/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.html b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.html new file mode 100644 index 0000000..d33d712 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.html @@ -0,0 +1,14 @@ +
+
+
Bofek Bodem {{ feature?.bodem }}
+
+ Periode:{{ feature?.periode }} + dmgdry/dmgwet:{{ feature?.dmgdry | number:'0.1-2'}}/{{ feature?.dmgwet | number:'0.1-2'}} + dmgind:{{ feature?.dmgind | number:'0.1-2'}} + gewas:{{ feature?.gewas}} + glg/ghg:{{ feature?.glg}}/{{ feature?.ghg}} + station:{{ feature?.station}} + hrvpotbio:{{ feature?.hrvpotbio}} +
+
+
diff --git a/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.ts b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.ts new file mode 100644 index 0000000..cadb62f --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-bofek/item-list-item-bofek.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Injectable } from '@angular/core'; +import { Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, ItemService, IListItem } from '@farmmaps/common'; +import { AbstractItemListItemComponent } from '../item-list-item/item-list-item.component' +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { ForSourceTask } from '../for-item/for-sourcetask.decorator'; + +@ForItemType("vnd.farmmaps.itemtype.shape.processed") +@ForSourceTask("vnd.farmmaps.task.bofek") +@Injectable() +@Component({ + selector: 'item-list-item-bofek', + templateUrl: './item-list-item-bofek.component.html', + styleUrls: ['./item-list-item-bofek.component.css'] +}) +export class ItemListItemBofekComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + feature; + + constructor(store: Store, itemTypeService: ItemTypeService, private itemService$: ItemService) { + super(store, itemTypeService); + } + ngOnInit() { + this.itemService$.getItem(this.item.code).subscribe(i => { + this.itemService$.getChildItemList(i.parentCode, "vnd.farmmaps.itemtype.trijntje").subscribe(t => { + if (t.length > 0) { + var data = t[0].data; + var bofekId = data["wwl-yieldloss-data"]["feature-with-largest-area"]; + var features = data["wwl-yieldloss-data"]["features"]; + for (var i = 0; i < features.length; i++) { + if (features[i]["bodem"] == bofekId) { + this.feature = features[i]; + return; + } + } + }; + }) + }) + }; +} diff --git a/projects/common-map/src/lib/components/item-list-item-container/item-list-item-container.component.ts b/projects/common-map/src/lib/components/item-list-item-container/item-list-item-container.component.ts new file mode 100644 index 0000000..ca88ff3 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-container/item-list-item-container.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleChanges, ComponentFactory, Inject, Type} from '@angular/core'; +import { AbstractItemListItemComponent,ItemListItemComponent } from '../item-list-item/item-list-item.component'; +import { WidgetHostDirective } from '../widget-host/widget-host.directive'; +import { IItem, IListItem } from '@farmmaps/common'; + + +@Component({ + selector: 'item-list-item-container', + template: ` +
+ +
+ ` +}) +export class ItemListItemContainerComponent { + + constructor(private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractItemListItemComponent) public itemComponentList: AbstractItemListItemComponent[] ) { + } + + @Input() item: IListItem; + + @ViewChild(WidgetHostDirective) widgetHost: WidgetHostDirective; + + loadComponent() { + var componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(ItemListItemComponent); // default + for (var i = 0; i < this.itemComponentList.length; i++) { + if (this.itemComponentList[i]['forItemType'] && + this.itemComponentList[i]['forItemType'].indexOf(this.item.itemType) >= 0 && + this.itemComponentList[i]['forSourceTask'] && + this.itemComponentList[i]['forSourceTask'].indexOf(this.item.sourceTask) >= 0 ) + { + componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.itemComponentList[i]['constructor'] as any); + } + } + const viewContainerRef = this.widgetHost.viewContainerRef; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).item = this.item; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes["item"] && changes["item"].currentValue) { + this.loadComponent(); + } + } +} diff --git a/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.css b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.css new file mode 100644 index 0000000..629e747 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.css @@ -0,0 +1,40 @@ +.head { + color: grey; + font-weight: bold; + /*border-bottom:1px solid black;*/ +} + +.body { + display: block; +} + +.mean { + text-align: center; + font-weight: bold; + font-size: 2rem; + line-height: 7.5rem; + color: deeppink; + /*border-right:1px solid black;*/ +} + +.min { + /*border-bottom:1px solid black;*/ +} + +.min, .max { + vertical-align: middle; + line-height: 3.48rem; + color: gray; + font-size: 1rem; +} + +.widget { + display: block; + height: 100%; + width: 100%; +} + +.unit { + font-size: 0.6rem; + color: gray; +} diff --git a/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.html b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.html new file mode 100644 index 0000000..e98e6fb --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.html @@ -0,0 +1,32 @@ +
+ +
+
+
+
Height
+
+
+
{{histogram.mean | number:'0.1-2'}}
+
+ +
{{(histogram.max+histogram.min)/2 | number:'0.1-2'}}
+
+
+
{{histogram.min | number:'0.1-2'}}
+
{{histogram.max | number:'0.1-2'}}
+
+
+
+
+
+
+ +
+
+
Height
+
+
No data available
+
+
+
+
diff --git a/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.ts b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.ts new file mode 100644 index 0000000..7578e67 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-height/item-list-item-height.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { Input, Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, Item, ItemService, FolderService, IListItem } from '@farmmaps/common'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { ForSourceTask } from '../for-item/for-sourcetask.decorator'; +import { AbstractItemListItemComponent } from '../item-list-item/item-list-item.component' +import { withLatestFrom, switchMap, map, switchMapTo, catchError, mergeMap, delay } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { ControlScaleLineComponent } from 'ngx-openlayers'; + + +@ForItemType("vnd.farmmaps.itemtype.geotiff.processed") +@ForSourceTask("vnd.farmmaps.task.ahn") +@Injectable() +@Component({ + selector: 'item-list-item-height', + templateUrl: './item-list-item-height.component.html', + styleUrls: ['./item-list-item-height.component.css'] +}) +export class ItemListItemHeightComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + selectedItem: Observable; + + constructor(store: Store, itemTypeService: ItemTypeService, private itemService$: ItemService) { + super(store, itemTypeService); + } + ngOnInit() { + this.selectedItem = this.itemService$.getItem(this.item.code); + } +} diff --git a/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.html b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.html new file mode 100644 index 0000000..0cf4ce9 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.html @@ -0,0 +1,40 @@ +
+
+
+ Shadow index +
+
+ + + + + + + + + + + + +
{{getMeanShadowValue(selectedItem) | number:'0.2-2'}}
+
+
+
diff --git a/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.scss b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.scss new file mode 100644 index 0000000..718c4e2 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.scss @@ -0,0 +1,17 @@ +.head { + color: grey; + font-weight: bold; +} + +.mean { + text-align: center; + font-weight: bold; + font-size: 1.9rem; + color: deeppink; +} + +.widget { + display: block; + height: 100%; + width: 100%; +} diff --git a/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.ts b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.ts new file mode 100644 index 0000000..a4f7512 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-shadow/item-list-item-shadow.component.ts @@ -0,0 +1,65 @@ +import {Observable} from 'rxjs'; +import {Component, Injectable, Input, OnInit} from '@angular/core'; +import {Store} from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import {commonReducers, IItem, IListItem, ItemService, ItemTypeService} from '@farmmaps/common'; +import {ForItemType} from '../for-item/for-itemtype.decorator'; +import {ForSourceTask} from '../for-item/for-sourcetask.decorator'; +import {AbstractItemListItemComponent} from '../item-list-item/item-list-item.component' +import {map} from "rxjs/operators"; + +@ForItemType("vnd.farmmaps.itemtype.geotiff.processed") +@ForSourceTask("vnd.farmmaps.task.shadow") +@Injectable() +@Component({ + selector: 'item-list-item-shadow', + templateUrl: './item-list-item-shadow.component.html', + styleUrls: ['./item-list-item-shadow.component.scss'] +}) +export class ItemListItemShadowComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + selectedItem: Observable; + + public sunCenter = {x: 235.9, y: 259.4}; + public sunStart = {x: 0, y: 0}; + public sunEndRelative = {x: 0, y: 0}; + public largeArc = 0; + public radius = 120; + + public sunColor = '#ffcc00'; + + constructor(store: Store, itemTypeService: ItemTypeService, + private itemService$: ItemService) { + super(store, itemTypeService); + } + + ngOnInit() { + this.selectedItem = this.itemService$.getItem(this.item.code) + .pipe(map(item => { + const shadowIndex = this.getMeanShadowValue(item); + this.calculateSunValues(shadowIndex); + return item; + })); + } + + calculateSunValues(shadowIndex) { + const dotProduct = shadowIndex * 2 - 1; + this.largeArc = dotProduct > 0 ? 1 : 0; + + const angle = Math.acos(dotProduct); + const yRadius = this.radius * Math.sin(-angle); + + this.sunStart.x = this.sunCenter.x + this.radius * dotProduct; + this.sunStart.y = this.sunCenter.y + yRadius; + this.sunEndRelative.x = this.sunStart.x; + this.sunEndRelative.y = this.sunCenter.y - yRadius; + } + + getSunShadowPath() { + return `M${this.sunStart.x} ${this.sunStart.y} A${this.radius},${this.radius} 0 ${this.largeArc},0 ${this.sunEndRelative.x} ${this.sunEndRelative.y}z`; + } + + getMeanShadowValue(item): number { + return item.data.layers[0].renderer.band.histogram.mean; + } +} diff --git a/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.css b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.css new file mode 100644 index 0000000..6c43ffd --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.css @@ -0,0 +1,42 @@ +.head { + color: grey; + font-weight:bold; + /*border-bottom:1px solid black;*/ +} + +.body { + display: block; +} + +.mean { + text-align: center; + font-weight: bold; + font-size:2rem; + line-height: 7.5rem; + color:deeppink; + /*border-right:1px solid black;*/ +} +.min { + /*border-bottom:1px solid black;*/ +} +.min, .max { + vertical-align: middle; + line-height: 3.48rem; + color:gray; + font-size:1rem; +} + +.widget { + display: block; + height: 100%; + width: 100%; +} + +.unit { + font-size:0.8rem; + color:gray; +} + +.dateformat { + font-size: 0.8rem; +} diff --git a/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.html b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.html new file mode 100644 index 0000000..6ca09f2 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.html @@ -0,0 +1,22 @@ +
+
+
+
+
Soil moisture (%)
+
{{lastChildItem.dataDate | date : 'shortDate'}}
+
+
+
{{histogram.mean/10 | number:'0.1-2'}}
+
+ +
{{(histogram.max+histogram.min)/2 | number:'0.1-2'}}
+
+
+
{{histogram.min/10 | number:'0.1-2'}}
+
{{histogram.max/10 | number:'0.1-2'}}
+
+
+
+
+
+
diff --git a/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.ts b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.ts new file mode 100644 index 0000000..e264b57 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-temporal/item-list-item-temporal.component.ts @@ -0,0 +1,42 @@ +import {Component, Injectable, Input, OnInit} from '@angular/core'; +import {Store} from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, ItemService, IListItem } from '@farmmaps/common'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { ForSourceTask } from '../for-item/for-sourcetask.decorator'; +import { AbstractItemListItemComponent } from '../item-list-item/item-list-item.component' +import { map, mergeMap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import {Router} from "@angular/router"; + + +@ForItemType("vnd.farmmaps.itemtype.temporal") +@ForSourceTask("vnd.farmmaps.task.vandersat") +@Injectable() +@Component({ + selector: 'item-list-item-temporal', + templateUrl: './item-list-item-temporal.component.html', + styleUrls: ['./item-list-item-temporal.component.css'] +}) +export class ItemListItemTemporalComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + lastTemporalItem$: Observable; + + constructor(store: Store, + itemTypeService: ItemTypeService, private itemService: ItemService, + private router: Router) { + super(store, itemTypeService); + } + ngOnInit() { + this.lastTemporalItem$ = this.itemService + .getChildItemList(this.item.code, "vnd.farmmaps.itemtype.geotiff.processed") + .pipe( + map(list => list.length>0? list.sort().slice(-1)[0]:null), + mergeMap(li => li==null?of(null):this.itemService.getItem(li.code))); + } + + onWidgetClicked() { + event.stopPropagation(); + this.router.navigate(['viewer/vandersat/item', this.item.code]); + } +} diff --git a/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.html b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.html new file mode 100644 index 0000000..6fa1fb8 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.html @@ -0,0 +1,13 @@ +
+ +
Yield forecast
+
+
(ton/ha)
+
{{dailyOutput.date | date : 'd/M/yy'}}
+
+
+
+
{{dailyOutput.tuberwt[1] | number:'0.1-1'}}
+
+
+
diff --git a/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.scss b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.scss new file mode 100644 index 0000000..34fcda0 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.scss @@ -0,0 +1,36 @@ +.head { + color: grey; + font-weight: bold; + /*border-bottom:1px solid black;*/ +} + +.body { + display: block; +} + +.value { + text-align: center; + font-weight: bold; + font-size: 1.9rem; + color: deeppink; + /*border-right:1px solid black;*/ +} + +.widget { + display: block; + height: 100%; + width: 100%; +} + +.unit { + font-size: 0.8rem; + color: gray; +} + +.dateformat { + font-size: 0.8rem; +} + +.fm-potato { + color: saddlebrown; +} diff --git a/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.ts b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.ts new file mode 100644 index 0000000..0179fef --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-tipstar/item-list-item-tipstar.component.ts @@ -0,0 +1,50 @@ +import {Component, Injectable, Input, OnInit} from '@angular/core'; +import {Store} from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import {commonReducers, IItem, IListItem, ItemService, ItemTypeService} from '@farmmaps/common'; +import {ForItemType} from '../for-item/for-itemtype.decorator'; +import {ForSourceTask} from '../for-item/for-sourcetask.decorator'; +import {AbstractItemListItemComponent} from '../item-list-item/item-list-item.component' +import {Observable} from "rxjs"; +import {Router} from "@angular/router"; + +@ForItemType("vnd.farmmaps.itemtype.tipstar") +@ForSourceTask("vnd.farmmaps.task.tipstar") +@Injectable() +@Component({ + selector: 'item-list-item-tipstar', + templateUrl: './item-list-item-tipstar.component.html', + styleUrls: ['./item-list-item-tipstar.component.scss'] +}) +export class ItemListItemTipstarComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + + item$: Observable; + + constructor(store: Store, itemTypeService: ItemTypeService, + private itemService$: ItemService, private router: Router) { + super(store, itemTypeService); + } + + ngOnInit() { + this.item$ = this.itemService$.getItem(this.item.code); + } + + onWidgetClicked(event: MouseEvent) { + event.stopPropagation(); + this.router.navigate(['viewer/tipstar/item', this.item.code]); + } + + getLastDailyOutput(item) { + if (item == null || item.data == null) { + return null; + } + + const itemData = item.data; + if (itemData.dailyOutput == null) { + return null; + } + + return itemData.dailyOutput[itemData.dailyOutput.length - 1] + } +} diff --git a/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.css b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.css new file mode 100644 index 0000000..d34a93a --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.css @@ -0,0 +1,35 @@ +.head { + color: grey; + font-weight: bold; + /*border-bottom:1px solid black;*/ +} + +.body { + display: block; +} + +.tpvocmet { + text-align: center; + font-weight: bold; + font-size: 1.9rem; + line-height: 7.2rem; + color: deeppink; + /*border-right:1px solid black;*/ +} + + + +.widget { + display: block; + height: 100%; + width: 100%; +} + +.unit { + font-size: 0.8rem; + color: gray; +} + +.dateformat { + font-size: 0.8rem; +} diff --git a/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.html b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.html new file mode 100644 index 0000000..d68353d --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.html @@ -0,0 +1,13 @@ +
+
+
WatBal (kg/ha)
+
+ Date:{{TPVOCMET?.Datum | date : 'shortDate'}} + Bedrijf/Perceel:{{TPVOCMET?.BedrijfID}}/{{TPVOCMET?.PerceelID}} + GewichtZonder:{{TPVOCMET?.GewichtZonder | number:'0.1-2'}} + GewichtGroDro:{{TPVOCMET?.GewichtGroDro | number:'0.1-2'}} + GewichtGroNat:{{TPVOCMET?.GewichtGroNat | number:'0.1-2'}} +
+ +
+
diff --git a/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.ts b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.ts new file mode 100644 index 0000000..0fd1d06 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item-watbal/item-list-item-watbal.component.ts @@ -0,0 +1,34 @@ +import { Observable, of } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; +import { Input, Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, Item, ItemService, IListItem } from '@farmmaps/common'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { ForSourceTask } from '../for-item/for-sourcetask.decorator'; +import { AbstractItemListItemComponent } from '../item-list-item/item-list-item.component' +import { Stage } from '../widget-status/widget-status.component'; + +@ForItemType("vnd.farmmaps.itemtype.watbal") +@ForSourceTask("vnd.farmmaps.task.watbal") +@Injectable() +@Component({ + selector: 'item-list-item-watbal', + templateUrl: './item-list-item-watbal.component.html', + styleUrls: ['./item-list-item-watbal.component.css'] +}) +export class ItemListItemWatBalComponent extends AbstractItemListItemComponent implements OnInit { + @Input() item: IListItem; + TPVOCMET; + stage = Stage.DevelopmentPreAlpha; + + constructor(store: Store, itemTypeService: ItemTypeService, private itemService$: ItemService) { + super(store, itemTypeService); + } + ngOnInit() { + this.itemService$.getItem(this.item.code).subscribe(i => { + var data = i.data.HTAKKER_Input.TPVOCMET; + this.TPVOCMET = data[data.length - 1]; + }); + } +} diff --git a/projects/common-map/src/lib/components/item-list-item/item-list-item.component.html b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.html new file mode 100644 index 0000000..2904dc3 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.html @@ -0,0 +1,4 @@ +
+
+
{{item.name}}
+
diff --git a/projects/common-map/src/lib/components/item-list-item/item-list-item.component.scss b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.scss new file mode 100644 index 0000000..ef6f471 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.scss @@ -0,0 +1,27 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.widget { + padding:0.8rem; + height:100%; + width:100%; + color:#ffffff; + position:relative; +} + +.icon { + display:block; + font-size:6rem; + text-align:center; +} +.title { + display:block; + position:absolute; + width:calc(100% - 1.6rem ); + padding-top:0.5rem; + bottom:0.8rem; + height:2rem; + overflow:hidden; + white-space:nowrap; + text-overflow:ellipsis; +} diff --git a/projects/common-map/src/lib/components/item-list-item/item-list-item.component.ts b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.ts new file mode 100644 index 0000000..973846a --- /dev/null +++ b/projects/common-map/src/lib/components/item-list-item/item-list-item.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, Injectable} from '@angular/core'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService,IListItem } from '@farmmaps/common'; + + + +@Injectable() +export abstract class AbstractItemListItemComponent { + @Input() item: IListItem + + constructor(public store: Store, public itemTypeService: ItemTypeService) { + } +} + +@Injectable() +export abstract class AbstractItemWidgetComponent { + @Input() item: IListItem + + constructor(public store: Store, public itemTypeService: ItemTypeService) { + } +} + +@Injectable() +@Component({ + selector: 'item-list-item', + templateUrl: './item-list-item.component.html', + styleUrls: ['./item-list-item.component.scss'] +}) +export class ItemListItemComponent extends AbstractItemListItemComponent { + + constructor(store: Store, itemTypeService: ItemTypeService) { + super(store, itemTypeService); + } +} diff --git a/projects/common-map/src/lib/components/item-list/item-list.component.html b/projects/common-map/src/lib/components/item-list/item-list.component.html new file mode 100644 index 0000000..741e4bc --- /dev/null +++ b/projects/common-map/src/lib/components/item-list/item-list.component.html @@ -0,0 +1,7 @@ +
+
+
+ +
+
+
diff --git a/projects/common-map/src/lib/components/item-list/item-list.component.scss b/projects/common-map/src/lib/components/item-list/item-list.component.scss new file mode 100644 index 0000000..f2aec84 --- /dev/null +++ b/projects/common-map/src/lib/components/item-list/item-list.component.scss @@ -0,0 +1,42 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.widget-container { + overflow:auto; + margin-bottom:1rem; +} + +.widget { + position:relative; + border: 1px solid gray('500'); + user-select: none; + display:inline-block; + width:50%; + overflow:hidden; + float:left; +} + +.widget:after { + content: ""; + display: block; + padding-bottom: 100%; +} + +.content { + position:absolute; + width:100%; + height:100%; +} + +.widget:hover { + background-color: gray('100'); +} + +.widget-container { + padding:1rem; +} + +.item-container { + display:block; + height:100%; +} diff --git a/projects/common-map/src/lib/components/item-list/item-list.component.ts b/projects/common-map/src/lib/components/item-list/item-list.component.ts new file mode 100644 index 0000000..da9f43f --- /dev/null +++ b/projects/common-map/src/lib/components/item-list/item-list.component.ts @@ -0,0 +1,43 @@ +import { Component, Input, Injectable } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService, IItem, Item,IListItem } from '@farmmaps/common'; +import * as mapActions from '../../actions/map.actions'; +import { Observable, from } from 'rxjs'; +import { withLatestFrom } from 'rxjs/operators'; +import { tassign } from 'tassign'; +import { IQueryState } from '../../models'; + + +@Injectable() +export abstract class AbstractItemListComponent { + @Input() items: Array + constructor(public store: Store, public itemTypeService: ItemTypeService, private location: Location) { + } + + handleItemClick(item:IListItem) { + var newQuery: any = tassign(mapReducers.initialState.query); + newQuery.itemCode = item.code; + this.store.dispatch(new mapActions.DoQuery(newQuery)); + } + + handleBackClick(event: MouseEvent) { + event.preventDefault(); + this.location.back(); + } +} + +@Injectable() +@Component({ + selector: 'item-list', + templateUrl: './item-list.component.html', + styleUrls: ['./item-list.component.scss'] +}) +export class ItemListComponent extends AbstractItemListComponent { + + constructor(store: Store, itemTypeService: ItemTypeService,location:Location) { + super(store, itemTypeService,location); + } +} diff --git a/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.html b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.html new file mode 100644 index 0000000..671e260 --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.html @@ -0,0 +1,7 @@ +
+
+
+ +
+
+
diff --git a/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.scss b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.scss new file mode 100644 index 0000000..f2aec84 --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.scss @@ -0,0 +1,42 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.widget-container { + overflow:auto; + margin-bottom:1rem; +} + +.widget { + position:relative; + border: 1px solid gray('500'); + user-select: none; + display:inline-block; + width:50%; + overflow:hidden; + float:left; +} + +.widget:after { + content: ""; + display: block; + padding-bottom: 100%; +} + +.content { + position:absolute; + width:100%; + height:100%; +} + +.widget:hover { + background-color: gray('100'); +} + +.widget-container { + padding:1rem; +} + +.item-container { + display:block; + height:100%; +} diff --git a/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.ts b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.ts new file mode 100644 index 0000000..67e09b3 --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-list/item-widget-list.component.ts @@ -0,0 +1,36 @@ +import { Component, Input, Injectable, Inject, ComponentFactoryResolver, ViewContainerRef, QueryList, ComponentFactory, ViewChildren,AfterViewInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService, IItem, Item,IListItem } from '@farmmaps/common'; +import { AbstractItemWidgetComponent } from '../item-list-item/item-list-item.component'; + + +@Injectable() +@Component({ + selector: 'item-widget-list', + templateUrl: './item-widget-list.component.html', + styleUrls: ['./item-widget-list.component.scss'] +}) +export class ItemWidgetListComponent implements AfterViewInit { + + @Input() item: IListItem; + public widgets: AbstractItemWidgetComponent[]; + @ViewChildren('widgetTemplate', { read: ViewContainerRef }) private widgetTargets: QueryList; + + constructor(public store: Store, public itemTypeService: ItemTypeService, public location: Location, private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractItemWidgetComponent) itemWidgetComponentList: AbstractItemWidgetComponent[]) { + this.widgets = itemWidgetComponentList; //todo filter this list on widgets available for user + } + + ngAfterViewInit() { + let targets = this.widgetTargets.toArray(); + for (var i = 0; i < this.widgets.length; i++) { + var componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(this.widgets[i]['constructor'] as any); + const viewContainerRef = targets[i]; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).item = this.item; + } + } +} diff --git a/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.html b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.html new file mode 100644 index 0000000..5a18e7b --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.html @@ -0,0 +1,4 @@ +
+
{{currentweather.temp}}°
+ +
diff --git a/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.scss b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.scss new file mode 100644 index 0000000..ef6f471 --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.scss @@ -0,0 +1,27 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.widget { + padding:0.8rem; + height:100%; + width:100%; + color:#ffffff; + position:relative; +} + +.icon { + display:block; + font-size:6rem; + text-align:center; +} +.title { + display:block; + position:absolute; + width:calc(100% - 1.6rem ); + padding-top:0.5rem; + bottom:0.8rem; + height:2rem; + overflow:hidden; + white-space:nowrap; + text-overflow:ellipsis; +} diff --git a/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.ts b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.ts new file mode 100644 index 0000000..191e3c2 --- /dev/null +++ b/projects/common-map/src/lib/components/item-widget-weather/item-widget-weather.component.ts @@ -0,0 +1,40 @@ +import { Component, Input, Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; +import { commonReducers, ItemTypeService, ItemService } from '@farmmaps/common'; +import { AbstractItemWidgetComponent} from '../item-list-item/item-list-item.component'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { HttpClient } from '@angular/common/http'; +import { GeoJSON } from 'ol/format'; +import { getCenter, Extent, createEmpty, extend } from 'ol/extent'; + +@Injectable() +@Component({ + selector: 'item-widget-weather', + templateUrl: './item-widget-weather.component.html', + styleUrls: ['./item-widget-weather.component.scss'] +}) +export class ItemWidgetWeatherComponent extends AbstractItemWidgetComponent { + + private _format: GeoJSON; + + constructor(store: Store, itemTypeService: ItemTypeService, public http: HttpClient, private itemService$: ItemService) { + super(store, itemTypeService); + this._format = new GeoJSON(); + } + + public weather: Observable; + + ngOnInit() { + this.weather = this.itemService$.getItem(this.item.code).pipe(mergeMap(i => { + console.debug(i.geometry); + var geometry = this._format.readGeometry(i.geometry); + var centroid = getCenter(geometry.getExtent()); + var url = 'https://weather.akkerweb.nl/v2/data/currentobservation/?c=' + centroid[0] + ',' + centroid[1] + '&key=5f17ef36283b49e9b099a1f4064fbf3d'; + var cw = this.http.get(url); + return cw; + })); + } +} diff --git a/projects/common-map/src/lib/components/item.list.component.ts b/projects/common-map/src/lib/components/item.list.component.ts new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/projects/common-map/src/lib/components/item.list.component.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/common-map/src/lib/components/legend/legend.component.html b/projects/common-map/src/lib/components/legend/legend.component.html new file mode 100644 index 0000000..726d617 --- /dev/null +++ b/projects/common-map/src/lib/components/legend/legend.component.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + +
+
+

{{layer.name}}

+ ({{layer.unit}}) +
+
+
+

{{histogram}}

+ ({{histogramunit}}) +
+
{{entry.value | number:'1.0-2'}} {{legendunit}}
+ + + + + diff --git a/projects/common-map/src/lib/components/legend/legend.component.scss b/projects/common-map/src/lib/components/legend/legend.component.scss new file mode 100644 index 0000000..4e5a61b --- /dev/null +++ b/projects/common-map/src/lib/components/legend/legend.component.scss @@ -0,0 +1,58 @@ + +.container { + max-width: 40em; + border-spacing: 0.5em; + font-family: inherit; +} + +.title { + padding: 7px; + text-align: center; + font-size: 14px; + line-height: 28px; +} + +.title > h4 { + margin-bottom: 0; +} + +.legend-items { + width: 1.5em; + height: 1.5em; + padding: 0.1em; + border-style: none; + border-width: 0.05em; +} + +.legend-items div { + width:100%; + height:100%; +} + +.legend-items-text { + max-width: 20em; + padding-left: 0.5em; + text-align: left; +} + +.histogram-items { + width: 5em; + padding-left: 1em; +} + + .histogram-items > div { + height: 1em; + border-style: solid; + border-width: 0.05em; + vertical-align: middle; + } + +.histogram-items-text { + max-width: 20em; + font-size: 14pt; + padding-left: 1em; + text-align: left; +} + + + diff --git a/projects/common-map/src/lib/components/legend/legend.component.ts b/projects/common-map/src/lib/components/legend/legend.component.ts new file mode 100644 index 0000000..f90c2cc --- /dev/null +++ b/projects/common-map/src/lib/components/legend/legend.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit, Input,AfterViewInit } from '@angular/core'; +import { IColorMap, IColor, IColorEntry,ILayer } from '../../models'; + + +@Component({ + selector: 'layer-legend', + templateUrl: './legend.component.html', + styleUrls: ['./legend.component.scss'] +}) +export class LegendComponent implements OnInit,AfterViewInit { + + constructor() { + + } + + ngOnInit() { + } + + ngAfterViewInit() { + } + + @Input() + layer: ILayer; + + @Input() + legend: string; + + @Input() + histogram: string; + + @Input() + showTitle: boolean = true; + + @Input() + histogramenabled: boolean; + + @Input() + legendunit: string; + + @Input() + histogramunit: string; + + + onClickHistoGram(): void { + this.histogramenabled = !this.histogramenabled; + } + + public getHex(color: IColor): string { + return '#' + this.componentToHex(color.red) + this.componentToHex(color.green) + this.componentToHex(color.blue); + } + + public componentToHex(c: number): string { + const hex = c.toString(16); + return hex.length === 1 ? `0${hex}` : hex; + } + + private getPart(colorMap: IColorMap, colorEntry: IColorEntry): string { + let sumOfValue = colorMap.entries.reduce((sum, item) => sum + item.value, 0); + let part = ((colorEntry.value / sumOfValue) * 100) + "%"; + + return part; + } + +} + + diff --git a/projects/common-map/src/lib/components/map-search/index.ts b/projects/common-map/src/lib/components/map-search/index.ts new file mode 100644 index 0000000..edb2ea1 --- /dev/null +++ b/projects/common-map/src/lib/components/map-search/index.ts @@ -0,0 +1 @@ +export * from './map-search.component'; diff --git a/projects/common-map/src/lib/components/map-search/map-search.component.html b/projects/common-map/src/lib/components/map-search/map-search.component.html new file mode 100644 index 0000000..4096301 --- /dev/null +++ b/projects/common-map/src/lib/components/map-search/map-search.component.html @@ -0,0 +1,33 @@ + + + + + + + diff --git a/projects/common-map/src/lib/components/map-search/map-search.component.scss b/projects/common-map/src/lib/components/map-search/map-search.component.scss new file mode 100644 index 0000000..019d434 --- /dev/null +++ b/projects/common-map/src/lib/components/map-search/map-search.component.scss @@ -0,0 +1,94 @@ +div.map-search { + position: absolute; + top: 0.5rem; + left: 0.5rem; + transition: opacity 0.5s ease-out, left 0.3s,max-height 0.3s ease-out,max-width 0.3s ease-out; + max-height: 10rem; + width: 21rem; + max-width: 21rem; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); + z-index:3; +} + +.input-group { + flex-wrap:nowrap; +} + +.disabled { + color:lighten(#000000,80%); +} + +:host ::ng-deep ngb-typeahead-window.dropdown-menu { + width: 20rem; + left:-2.5rem !important; +} + +:host ::ng-deep button.dropdown-item { + overflow:hidden; + text-overflow:ellipsis; + padding: 0.375rem 0.75rem; +} + +div.map-search input[type=text] { + transition: width 0.3s ease-out; +} + +div.map-search button { + transition: width 0.3s ease-out; +} + +div.map-search.collapsed { + max-height:3.5rem; + max-width:10rem; +} + +div.map-search.searchcollapsed { + max-height: 3.5rem; + max-width: 6rem; +} + +div.map-search div.options { + padding-top: 0.5rem; + line-height: 1.5rem; + overflow: hidden; + transition: max-height 0.3s ease-in-out; + max-height:4.5rem; +} + +div.map-search.collapsed div.options, div.map-search.searchcollapsed div.options { + max-height: 0; + padding:0; +} + +div.map-search button { + overflow: hidden; +} + +div.map-search.collapsed input[type=text] { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +div.map-search.searchcollapsed input[type=text] { + display: none; +} + +div.map-search.collapsed button.clear, div.map-search.collapsed button[type="submit"] { + width: 0; + padding: 0; + border-color:transparent; +} + +div.map-search.searchcollapsed button[type="submit"] { + width: 0; + padding: 0; + border-color: transparent; +} + +.options label { + white-space:nowrap; +} + + + + diff --git a/projects/common-map/src/lib/components/map-search/map-search.component.ts b/projects/common-map/src/lib/components/map-search/map-search.component.ts new file mode 100644 index 0000000..aac3a2f --- /dev/null +++ b/projects/common-map/src/lib/components/map-search/map-search.component.ts @@ -0,0 +1,178 @@ +import { Component, Input, Output, OnInit, EventEmitter, SimpleChanges, OnChanges, ViewChild } from '@angular/core'; +import { Observable , of } from 'rxjs'; +import { debounceTime,distinctUntilChanged,tap,switchMap,merge,catchError} from 'rxjs/operators'; +import { TypeaheadService, TimespanService } from '@farmmaps/common'; +import { IQueryState,IPeriodState } from '../../models'; +import { fillProperties } from '@angular/core/src/util/property'; +import { tassign } from 'tassign'; + +@Component({ + selector: 'map-search', + templateUrl: './map-search.component.html', + styleUrls: ['./map-search.component.scss'] +}) +export class MapSearchComponent { + + @ViewChild('searchText') searchText; + @Input() clearEnabled: boolean + @Input() set collapsed(collapsed: boolean) { + this.collapsedLocal = collapsed; + if (collapsed) this.searchText.nativeElement.blur(); + } + @Input() set searchMinified(minified: boolean) { + this.searchMinifiedLocal = minified; + } + @Input() period: IPeriodState + @Output() onSearch = new EventEmitter(); + @Output() onClear = new EventEmitter(); + @Output() onSearchCollapse = new EventEmitter(); + @Output() onSearchExpand = new EventEmitter(); + @Output() onToggleMenu = new EventEmitter(); + @Output() onOpenModal = new EventEmitter(); + @Output() onCloseModal = new EventEmitter(); + @Input() openedModalName: string; + @Input() set filterOptions(filterOptions: IQueryState) { + if (filterOptions && filterOptions.query && filterOptions.query.length > 0) { + this.disabled = false; + } else { + this.disabled = true; + } + this.filterOptionsLocal = tassign(this.filterOptionsLocal, { tags: filterOptions.tags, query: filterOptions.query,bbox:filterOptions.bbox }); + if (filterOptions.tags) { + this.searchTextLocal = { name: filterOptions.tags }; + } else { + this.searchTextLocal = { name: filterOptions.query }; + } + if (this.dateFilter) { + this.filterOptionsLocal.startDate = this.startDate; + this.filterOptionsLocal.endDate = this.endDate; + } + } + + public collapsedLocal: boolean = true; + public searchMinifiedLocal: boolean = false; + public filterOptionsLocal: IQueryState; + private extent: number[]; + public searchTextLocal: any; + public searchTextLocalOutput: string; + public dateFilter: boolean = true; + public startDate: Date = new Date(new Date(Date.now()).getFullYear(), new Date(Date.now()).getMonth() - 3, 1); + public endDate: Date = new Date(Date.now()); + public startEndCaption: string = this.timespanService.getCaption(this.startDate, this.endDate, 4); + + searching = false; + searchFailed = false; + hideSearchingWhenUnsubscribed = new Observable(() => () => this.searching = false); + + public disabled: boolean = true; + + constructor(private typeaheadService: TypeaheadService, private timespanService: TimespanService) { + this.filterOptionsLocal = { query: "", tags: "", startDate: null, endDate: null, bboxFilter: true, itemType: null, itemCode:null,level:0,parentCode:null,bbox:[] }; + } + + search = (text$: Observable) => + text$.pipe( + debounceTime(300), + distinctUntilChanged(), + tap(() => this.searching = true), + switchMap(term => term.length < 1 ? of([]) : + this.typeaheadService.getSearchTypeaheadItems(term).pipe( + tap(() => this.searchFailed = false), + catchError(() => { + this.searchFailed = true; + return of([]); + })) ), + tap(() => this.searching = false), + merge(this.hideSearchingWhenUnsubscribed)); + + formatter = (x: { name: string }) => x.name; + + handleSearch(event) { + this.filterOptionsLocal.tags = null; + this.filterOptionsLocal.itemType = null; + this.filterOptionsLocal.itemCode = null; + this.filterOptionsLocal.parentCode = null; + this.filterOptionsLocal.query = this.searchTextLocalOutput; + if (this.dateFilter) { + this.filterOptionsLocal.startDate = this.startDate; + this.filterOptionsLocal.endDate = this.endDate; + } + this.onSearch.emit(this.filterOptionsLocal); + } + + handleOpenSelectPeriodModal(event: MouseEvent) { + event.preventDefault(); + this.onOpenModal.emit('selectPeriodModal'); + } + + handleCloseModal() { + this.onCloseModal.emit({}); + } + + handleSelect(event) { + event.preventDefault(); + this.filterOptionsLocal.query = null; + this.filterOptionsLocal.itemType = null; + this.filterOptionsLocal.itemCode = null; + this.filterOptionsLocal.parentCode = null; + this.filterOptionsLocal.tags = event.item.name; + if (this.dateFilter) { + this.filterOptionsLocal.startDate = this.startDate; + this.filterOptionsLocal.endDate = this.endDate; + } + this.onSearch.emit(this.filterOptionsLocal); + this.searchTextLocal = { name: this.filterOptionsLocal.tags }; + } + + handleSelectPeriod(event: { startDate: Date, endDate: Date }) { + this.startDate = event.startDate; + this.endDate = event.endDate; + this.handleCloseModal(); + this.startEndCaption = this.timespanService.getCaption(event.startDate, event.endDate, 4); + this.onSearch.emit(this.filterOptionsLocal); + } + + handleChangeEnableDateFilter(enabled) { + this.dateFilter = enabled; + if (enabled) { + this.filterOptionsLocal.startDate = this.startDate; + this.filterOptionsLocal.endDate = this.endDate; + } else { + this.filterOptionsLocal.startDate = null; + this.filterOptionsLocal.endDate = null; + } + if(this.filterOptionsLocal.query || this.filterOptionsLocal.tags) + this.onSearch.emit(this.filterOptionsLocal); + } + + handleChangeEnableBBOXFilter(enabled) { + this.filterOptionsLocal.bboxFilter = enabled; + if (this.filterOptionsLocal.query || this.filterOptionsLocal.tags) + this.onSearch.emit(this.filterOptionsLocal); + } + + + handleToggleMenu(event) { + this.onToggleMenu.emit({}); + } + + handleFocus(event) { + this.onSearchExpand.emit({}); + } + + handleChange(event: string) { + this.searchTextLocalOutput = event; + if (event && event.length == 1) { + this.onSearchExpand.emit({}); + } + if (event && event.length > 0) + this.disabled = false; + else + this.disabled = true; + } + + handleClearClick(event) { + this.onClear.emit({}); + } +} + diff --git a/projects/common-map/src/lib/components/map/map.component.html b/projects/common-map/src/lib/components/map/map.component.html new file mode 100644 index 0000000..d72ddc4 --- /dev/null +++ b/projects/common-map/src/lib/components/map/map.component.html @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + +
+ + +
+ +
+ + +
+
+
+
+ +
+ +
+ +
+ +
+
+
Cannot find {{(queryState|async)?.query}}
+
Cannot find tag {{(queryState|async)?.tags}}
+
+
+
+
+ +
+
+ + + + + +
+
+
+ diff --git a/projects/common-map/src/lib/components/map/map.component.scss b/projects/common-map/src/lib/components/map/map.component.scss new file mode 100644 index 0000000..8b4f02a --- /dev/null +++ b/projects/common-map/src/lib/components/map/map.component.scss @@ -0,0 +1,171 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +aol-map { position:absolute;width:100%;height:100%;} + +.arrow { + top: 3rem; +} + +.popover { + max-height:22rem; + min-width:15rem; +} + +.popover-body { + max-height:19rem; + overflow:hidden; + overflow-y:auto; +} + +.card-title { + font-size:1rem; +} + +.menu-card { + margin-left:-7px; + padding-left:7px; + margin-right:-7px; + padding-right:7px; + margin-bottom:7px; +} + +.icon-top { + font-size: 6rem; + text-align: center; + height: 9.75rem; +} + +.icon-top i { + padding-top: 1.875rem; +} + +.card-title { + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; +} + + +.control-container { + position: absolute; + right: 1em; + bottom: 1em; +} + +switch2d3d { + position: absolute; + right: 1em; + bottom: 1em; +} + +.panel-wrapper { + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + height: 100%; +} + +.panel-top { + display: block; + height:0; +} + +.panel-bottom { + overflow:auto; + flex:1; +} + +.no-results { + font-weight:bold; +} + +.no-results > span { + font-style:italic; +} + +div.logo { + display:flex; + padding-top:1em; + margin-bottom:1em; +} + +div.logo img { + width:100%; + align-self:center; +} + +div.logo button { + margin-left:1em; +} + +timespan { + position: absolute; + transition: left 0.5s; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(255, 255, 255, 0.5); +} + +timespan.menuVisible { + left: 22rem; +} + +:host ::ng-deep .timespan div.clearfix { + transition: height 0.5s; +} + +:host ::ng-deep .menu .side-panel { + z-index: 100; + background-color: rgb(245,245,245); +} + +@media screen and (min-width:44rem) { + .panel-top { + height: 8.1rem; + } +} + +.map { + transition: margin-left 0.3s; +} + +.shortcut-icon { + display: inline-block; + text-align: center; + margin: 0.5rem; + cursor:pointer; +} + +.shortcut-icon > .icon { + width: 3rem; + height: 3rem; + background-color: #731e64; + display: inline-block; + text-align: center; + line-height: 3rem; + color: #ffffff; + font-size: 2rem; +} + +.shortcut-icon > .caption { + text-align: center; + width: 4rem; + overflow: hidden; + text-overflow: ellipsis; + color: theme-color(); +} + +.shortcut-icon > .farm-icon { + background-color: #731E64; +} + +.shortcut-icon > .trijntje-icon { + background-color: #FAA33F; +} + +/*.panel-visible { + margin-left:22rem; +}*/ diff --git a/projects/common-map/src/lib/components/map/map.component.ts b/projects/common-map/src/lib/components/map/map.component.ts new file mode 100644 index 0000000..dcea912 --- /dev/null +++ b/projects/common-map/src/lib/components/map/map.component.ts @@ -0,0 +1,297 @@ +import { Component, OnInit, OnDestroy, HostListener, Inject, ViewChild, AfterViewInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { Observable, Subject, Subscription,combineLatest, from } from 'rxjs'; +import { debounce, withLatestFrom, first, combineAll } from 'rxjs/operators'; +import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router'; +import { Store } from '@ngrx/store'; +//import { proj,Map } from 'openlayers'; + +// Map +import * as mapReducers from '../../reducers/map.reducer'; +import * as mapActions from '../../actions/map.actions'; +import { IMapState,ISelectedFeatures,IItemLayer, ItemLayer,IQueryState,IPeriodState } from '../../models'; +import { IDroppedFile } from '../aol/file-drop-target/file-drop-target.component'; +import { IMetaData } from '../meta-data-modal/meta-data-modal.component'; +import { StateSerializerService } from '../../services/state-serializer.service'; +import { GeolocationService} from '../../services/geolocation.service'; + +// AppCommon +import { ResumableFileUploadService, ItemTypeService } from '@farmmaps/common'; +import { IItemType, IItem } from '@farmmaps/common'; +import {commonReducers} from '@farmmaps/common'; +import {commonActions} from '@farmmaps/common'; + +import {Feature} from 'ol'; +import {Extent,createEmpty,extend } from 'ol/extent'; +import {transform} from 'ol/proj'; +import { query } from '@angular/animations'; +import { tassign } from 'tassign'; + + +@Component({ + selector: 'map', + templateUrl: './map.component.html', + styleUrls: ['./map.component.scss'] +}) + +export class MapComponent implements OnInit, OnDestroy,AfterViewInit { + title: string = 'Map'; + public openedModalName: Observable; + public itemTypes: Observable<{ [id: string]: IItemType }>; + public mapState: Observable; + public features: Observable>; + public overlayLayers: Observable>; + public selectedOverlayLayer: Observable; + public selectedItemLayer: Observable; + public baseLayers: Observable>; + public selectedBaseLayer: Observable; + public projection: Observable; + public selectedFeatures: Subject = new Subject(); + public droppedFile: Subject = new Subject(); + private paramSub: Subscription; + private itemTypeSub: Subscription; + private mapStateSub: Subscription; + private queryStateSub: Subscription; + public parentCode: Observable; + public panelVisible: Observable; + public panelCollapsed: Observable; + public selectedFeature: Observable; + public selectedItem: Observable; + public queryState: Observable; + public period: Observable; + public clearEnabled: Observable; + public searchCollapsed: Observable; + public searchMinified: Observable; + public menuVisible: Observable; + public query: Observable; + public position: Observable; + public baseLayersCollapsed:boolean = true; + public overlayLayersCollapsed: boolean = true; + public extent: Observable; + @ViewChild('map') map; + + constructor(private store: Store, private route: ActivatedRoute, private router: Router, private uploadService: ResumableFileUploadService, private serializeService: StateSerializerService, public itemTypeService: ItemTypeService, private location: Location, private geolocationService: GeolocationService ) { + } + + @HostListener('document:keyup', ['$event']) + escapeClose(event: KeyboardEvent) { + let x = event.keyCode; + if (x === 27) { + this.handleCloseModal() + } + } + + handleOpenModal(modalName: string) { + this.store.dispatch(new commonActions.OpenModal(modalName)); + } + + handleCloseModal() { + this.store.dispatch(new commonActions.CloseModal()); + } + + handleFileDropped(droppedFile: IDroppedFile) { + this.uploadService.addFiles(droppedFile.files, droppedFile.event, { parentCode:droppedFile.parentCode, geometry:droppedFile.geometry }); + } + + handleFeaturesSelected(feature: Feature) { + if (feature) { + let newQuery = tassign(mapReducers.initialQueryState, { itemCode: feature.get('code') }); + this.store.dispatch(new mapActions.DoQuery(newQuery)); + } + } + + handleSearch(queryState: IQueryState) { + this.store.dispatch(new mapActions.DoQuery(queryState)); + } + + ngOnInit() { + this.store.dispatch(new mapActions.Init()); + this.selectedFeatures.next({x:0,y:0,features:[]}); + this.mapState = this.store.select(mapReducers.selectGetMapState); + this.parentCode = this.store.select(mapReducers.selectGetParentCode); + this.features = this.store.select(mapReducers.selectGetFeatures); + this.overlayLayers = this.store.select(mapReducers.selectGetOverlayLayers); + this.selectedOverlayLayer = this.store.select(mapReducers.selectGetSelectedOverlayLayer); + this.baseLayers = this.store.select(mapReducers.selectGetBaseLayers); + this.projection = this.store.select(mapReducers.selectGetProjection); + this.selectedBaseLayer = this.store.select(mapReducers.selectGetSelectedBaseLayer); + this.panelVisible = this.store.select(mapReducers.selectGetPanelVisible); + this.panelCollapsed = this.store.select(mapReducers.selectGetPanelCollapsed); + this.selectedFeature = this.store.select(mapReducers.selectGetSelectedFeature); + this.selectedItem = this.store.select(mapReducers.selectGetSelectedItem); + this.queryState = this.store.select(mapReducers.selectGetQueryState); + this.clearEnabled = this.store.select(mapReducers.selectGetClearEnabled); + this.searchCollapsed = this.store.select(mapReducers.selectGetSearchCollapsed); + this.searchMinified = this.store.select(mapReducers.selectGetSearchMinified); + this.menuVisible = this.store.select(mapReducers.selectGetMenuVisible); + this.openedModalName = this.store.select(commonReducers.selectOpenedModalName); + this.query = this.store.select(mapReducers.selectGetQuery); + this.extent = this.store.select(mapReducers.selectGetExtent); + this.selectedFeatures.next(null); + this.selectedItemLayer = this.store.select(mapReducers.selectGetSelectedItemLayer); + this.period = this.store.select(mapReducers.selectGetPeriod); + this.position = this.geolocationService.getCurrentPosition(); + + this.mapState.pipe(withLatestFrom(this.queryState)).subscribe((state) => { + this.replaceUrl(state[0], state[1], true); + }); + this.query.pipe(withLatestFrom(this.mapState)).subscribe((state) => { + this.replaceUrl(state[1], state[0],false); + }); + } + + private stateSetCount: number = 0; + private lastQueryState: string = this.serializeService.serialize(mapReducers.initialQueryState); + private lastMapState: string = ""; + ngAfterViewInit() { + this.paramSub = this.route.paramMap.subscribe((params: ParamMap) => { + //console.log("Param sub"); + var newMapState: IMapState = null; + var newQueryState: IQueryState = null; + var mapStateChanged = false; + var queryStateChanged = false; + if (params.has("xCenter") && params.has("yCenter")) { + let xCenter = parseFloat(params.get("xCenter")); + let yCenter = parseFloat(params.get("yCenter")); + let zoom = parseFloat(params.get("zoom")); + let rotation = parseFloat(params.get("rotation")); + let baseLayer = params.get("baseLayer"); + newMapState = { xCenter: xCenter, yCenter: yCenter, zoom: zoom, rotation: rotation, baseLayerCode: baseLayer } + mapStateChanged = this.lastMapState != JSON.stringify(newMapState) && this.stateSetCount == 0; + this.lastMapState = JSON.stringify(newMapState); + //console.log(`Base layer: ${newMapState.baseLayerCode}`) + } + if (params.has("queryState")) { + let queryState = params.get("queryState"); + newQueryState = tassign(mapReducers.initialQueryState); + if (queryState != "") { + newQueryState = this.serializeService.deserialize(queryState); + } + queryStateChanged = this.lastQueryState != queryState; + this.lastQueryState = queryState; + } + + if (mapStateChanged && queryStateChanged) { + //console.log("Both states"); + this.store.dispatch(new mapActions.SetState(newMapState, newQueryState)); + } else if (mapStateChanged) { + //console.log("Map state"); + this.store.dispatch(new mapActions.SetMapState(newMapState)); + } else if (queryStateChanged) { + //console.log("Query state"); + this.store.dispatch(new mapActions.SetQueryState(newQueryState)); + } + this.stateSetCount += 1; + }); + setTimeout(() => { + this.map.instance.updateSize(); + }, 500); + } + + handleSearchCollapse(event) { + this.store.dispatch(new mapActions.CollapseSearch()); + } + + handleSearchExpand(event) { + this.store.dispatch(new mapActions.ExpandSearch()); + } + + handleToggleMenu(event) { + this.store.dispatch(new mapActions.ToggleMenu()); + } + + handleToggleBaseLayers(event:MouseEvent) { + this.baseLayersCollapsed = !this.baseLayersCollapsed; + event.preventDefault(); + } + + handleToggleOverlayLayers(event: MouseEvent) { + this.overlayLayersCollapsed = !this.overlayLayersCollapsed; + event.preventDefault(); + } + + handlePredefinedQuery(event: MouseEvent, query: any) { + event.preventDefault(); + var queryState = tassign(mapReducers.initialQueryState, query); + this.store.dispatch(new mapActions.DoQuery(queryState)); + } + + handleTrijntjeClick(event: MouseEvent, query: any) { + event.preventDefault(); + var queryState = tassign(mapReducers.initialQueryState, query); + var mapState = JSON.parse(this.lastMapState); + this.router.navigate(["app","trijntje" , mapState.xCenter.toFixed(5), mapState.yCenter.toFixed(5), mapState.zoom, mapState.rotation.toFixed(2), mapState.baseLayerCode, this.serializeService.serialize(queryState)], { replaceUrl: false }); + } + + replaceUrl(mapState: IMapState, queryState: IQueryState, replace: boolean = true) { + //console.log(`Replace url :${mapState.baseLayerCode}`) + this.router.navigate([".", mapState.xCenter.toFixed(5), mapState.yCenter.toFixed(5), mapState.zoom, mapState.rotation.toFixed(2), mapState.baseLayerCode, this.serializeService.serialize(queryState)], { replaceUrl: replace,relativeTo:this.route.parent.parent }); + } + + handleOnMoveEnd(event) { + var map = event.map; + var view = map.getView(); + var rotation = view.getRotation(); + var zoom = view.getZoom(); + var center = transform(view.getCenter(), view.getProjection(), "EPSG:4326"); + var extent = view.calculateExtent(this.map.instance.getSize()); + let mapState: IMapState = { xCenter: center[0], yCenter: center[1], zoom: zoom, rotation: rotation, baseLayerCode: null }; + let state = { mapState: mapState, extent: extent }; + let source = from([state]); + source.pipe(withLatestFrom(this.selectedBaseLayer), withLatestFrom(this.queryState)).subscribe(([[state, baselayer], queryState]) => { + if (mapState && baselayer && queryState) { + let newMapState = tassign(state.mapState, { baseLayerCode: baselayer.item.code }); + this.replaceUrl(newMapState, tassign(queryState, { bbox: queryState.bboxFilter ? state.extent : queryState.bbox })); + this.store.dispatch(new mapActions.SetViewExtent(state.extent)); + } + }); + } + + handleOnMouseDown(event: MouseEvent) { + this.store.dispatch(new mapActions.CollapseSearch()); + } + + + + handleOnDownload(event) { + + } + + handleClearSearch(event) { + this.store.dispatch(new commonActions.Escape(true, false)); + } + + handleOnDelete(itemLayer: IItemLayer) { + this.store.dispatch(new mapActions.RemoveLayer(itemLayer)); + } + + handleOnToggleVisibility(itemLayer: IItemLayer) { + this.store.dispatch(new mapActions.SetVisibility(itemLayer,!itemLayer.visible)); + } + + handleOnSetOpacity(event:{ layer: IItemLayer,opacity:number }) { + this.store.dispatch(new mapActions.SetOpacity(event.layer, event.opacity)); + } + + handleZoomToExtent(itemLayer: IItemLayer) { + var extent = createEmpty(); + extend(extent, itemLayer.layer.getExtent()); + if (extent) { + this.store.dispatch(new mapActions.SetExtent(extent)); + } + } + + handleSelectBaseLayer(itemLayer: IItemLayer) { + this.store.dispatch(new mapActions.SelectBaseLayer(itemLayer)); + } + + handleSelectOverlayLayer(itemLayer: IItemLayer) { + this.store.dispatch(new mapActions.SelectOverlayLayer(itemLayer)); + } + + ngOnDestroy() { + this.paramSub.unsubscribe(); + if (this.itemTypeSub) this.itemTypeSub.unsubscribe(); + if (this.mapStateSub) this.mapStateSub.unsubscribe(); + if (this.queryStateSub) this.queryStateSub.unsubscribe(); } +} diff --git a/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.html b/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.html new file mode 100644 index 0000000..c81a936 --- /dev/null +++ b/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.html @@ -0,0 +1,19 @@ + +
+ + + +
+
+ + diff --git a/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.ts b/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.ts new file mode 100644 index 0000000..7423614 --- /dev/null +++ b/projects/common-map/src/lib/components/meta-data-modal/meta-data-modal.component.ts @@ -0,0 +1,61 @@ +import { Component, Output, ViewChild, EventEmitter, Input, ElementRef, HostListener } from '@angular/core'; +import { FormGroup,FormBuilder, Validators } from '@angular/forms'; +import { IListItem } from '@farmmaps/common'; +import { IDroppedFile } from '../aol/file-drop-target/file-drop-target.component'; +import {NgbModal, NgbModalRef} from "@ng-bootstrap/ng-bootstrap"; + +export interface IMetaData { + droppedFile: IDroppedFile, + attributes: any +} + +@Component({ + selector: 'meta-data-modal', + templateUrl: 'meta-data-modal.component.html' +}) +export class MetaDataModalComponent { + + private modalName: string = 'metaDataModal'; + private modalRef: NgbModalRef; + + @ViewChild('content') _templateModal:ElementRef; + @Input() droppedFile: IDroppedFile; + @Input() set modalState(_modalState:any) {; + if(_modalState == this.modalName) { + this.openModal() + } else if(this.modalRef) { + this.closeModal(); + } + } + + @Input() event: IListItem; + + @Output() onCloseModal = new EventEmitter(); + @Output() onAddFilesWithMetaData = new EventEmitter(); + + constructor(private modalService: NgbModal, public fb: FormBuilder) { } + + public metaDataForm: FormGroup; + + handleMetaDataEntered(event) { + if (this.metaDataForm.valid) { + this.onAddFilesWithMetaData.emit({ droppedFile: this.droppedFile, attributes: { name: this.metaDataForm.value.name } }); + } + } + + openModal() { + //Timeout trick to avoid ExpressionChangedAfterItHasBeenCheckedError + setTimeout(() => this.modalRef = this.modalService.open(this._templateModal, { backdrop: 'static', keyboard: false })); + } + + closeModal() { + this.modalRef.close(); + this.metaDataForm.patchValue({ name: "" }); + } + + ngOnInit(): void { + this.metaDataForm = this.fb.group({ + name: ["", Validators.compose([Validators.required, Validators.pattern("[a-zA-Z0-9_].*")])] + }); + } +} diff --git a/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.html b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.html new file mode 100644 index 0000000..0fbc85b --- /dev/null +++ b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.html @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.scss b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.scss new file mode 100644 index 0000000..3b024d8 --- /dev/null +++ b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.scss @@ -0,0 +1,20 @@ +.custom-day { + text-align: center; + padding: 0.185rem 0.25rem; + display: inline-block; + height: 2rem; + width: 2rem; +} + +.custom-day.focused { + background-color: #e6e6e6; +} + +.custom-day.range, .custom-day:hover { + background-color: rgb(2, 117, 216); + color: white; +} + +.custom-day.faded { + background-color: rgba(2, 117, 216, 0.5); +} diff --git a/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.ts b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.ts new file mode 100644 index 0000000..b5cc00b --- /dev/null +++ b/projects/common-map/src/lib/components/select-period-modal/select-period-modal.component.ts @@ -0,0 +1,93 @@ +import { Component, Output, ViewChild, EventEmitter, Input, ElementRef, HostListener } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { NgbModal, NgbModalRef, NgbDateStruct, NgbCalendar, NgbDateAdapter } from "@ng-bootstrap/ng-bootstrap"; +import { NgbDateNativeAdapter } from '@farmmaps/common'; + + +const equals = (one: NgbDateStruct, two: NgbDateStruct) => + one && two && two.year === one.year && two.month === one.month && two.day === one.day; + +const before = (one: NgbDateStruct, two: NgbDateStruct) => + !one || !two ? false : one.year === two.year ? one.month === two.month ? one.day === two.day + ? false : one.day < two.day : one.month < two.month : one.year < two.year; + +const after = (one: NgbDateStruct, two: NgbDateStruct) => + !one || !two ? false : one.year === two.year ? one.month === two.month ? one.day === two.day + ? false : one.day > two.day : one.month > two.month : one.year > two.year; + +@Component({ + selector: 'select-period-modal', + templateUrl: 'select-period-modal.component.html', + styleUrls: ['select-period-modal.component.scss'] +}) +export class SelectPeriodModalComponent { + + private modalName: string = 'selectPeriodModal'; + private modalRef: NgbModalRef; + private dateAdapter = new NgbDateNativeAdapter(); + hoveredDate: NgbDateStruct; + fromDate: NgbDateStruct; + toDate: NgbDateStruct; + + @ViewChild('content') _templateModal:ElementRef; + + @Input() set modalState(_modalState:any) {; + if(_modalState == this.modalName) { + this.openModal() + } else if(this.modalRef) { + this.closeModal(); + } + } + + @Input() set startDate(_modalState: Date) { + this.fromDate = this.dateAdapter.fromModel(_modalState); + } + + @Input() set endDate(_modalState: Date) { + var d = new Date(_modalState); + d.setDate(d.getDate() - 1); + this.toDate = this.dateAdapter.fromModel(d); + } + + @Output() onCloseModal = new EventEmitter(); + @Output() onSelect = new EventEmitter<{ startDate: Date, endDate: Date }>(); + + constructor(private modalService: NgbModal, private calendar: NgbCalendar) { } + + openModal() { + //Timeout trick to avoid ExpressionChangedAfterItHasBeenCheckedError + setTimeout(() => this.modalRef = this.modalService.open(this._templateModal, { backdrop: 'static', keyboard: false})); + } + + closeModal() { + this.modalRef.close(); + } + + ngOnInit(): void { + } + + onDateChange(date: NgbDateStruct) { + if (!this.fromDate && !this.toDate) { + this.fromDate = date; + } else if (this.fromDate && !this.toDate && after(date, this.fromDate)) { + this.toDate = date; + } else { + this.toDate = null; + this.fromDate = date; + } + } + + handleSelect(event:MouseEvent) { + event.preventDefault(); + if (this.fromDate && this.toDate && before(this.fromDate, this.toDate)) { + var endDate = new Date(this.dateAdapter.toModel(this.toDate)); + endDate.setDate(endDate.getDate() + 1); + this.onSelect.emit({startDate:this.dateAdapter.toModel(this.fromDate),endDate:endDate}) + } + } + + isHovered = date => this.fromDate && !this.toDate && this.hoveredDate && after(date, this.fromDate) && before(date, this.hoveredDate); + isInside = date => after(date, this.fromDate) && before(date, this.toDate); + isFrom = date => equals(date, this.fromDate); + isTo = date => equals(date, this.toDate); +} diff --git a/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.html b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.html new file mode 100644 index 0000000..a49f860 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.scss b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.scss new file mode 100644 index 0000000..ea36fef --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.scss @@ -0,0 +1,14 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + + + +.row { + border-bottom: 1px solid gray('500'); + user-select: none; +} + +.row:hover { + background-color: gray('100'); +} + diff --git a/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.ts b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.ts new file mode 100644 index 0000000..a8747d7 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-container/selected-item-container.component.ts @@ -0,0 +1,40 @@ +import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleChanges, ComponentFactory, Inject, Type} from '@angular/core'; +import { IItem } from '@farmmaps/common'; +import { AbstractSelectedItemComponent, SelectedItemComponent } from '../selected-item/selected-item.component'; +import { WidgetHostDirective } from '../widget-host/widget-host.directive'; + + +@Component({ + selector: 'selected-item-container', + templateUrl: './selected-item-container.component.html', + styleUrls: ['./selected-item-container.component.scss'] +}) +export class SelectedItemContainerComponent { + + constructor(private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractSelectedItemComponent) public selectedItemComponents: AbstractSelectedItemComponent[] ) { + } + + @Input() item: IItem; + + @ViewChild(WidgetHostDirective) widgetHost: WidgetHostDirective; + + loadComponent() { + var componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(SelectedItemComponent); // default + for (var i = 0; i < this.selectedItemComponents.length; i++) { + if (this.selectedItemComponents[i]['forItemType'] == this.item.itemType) { + componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.selectedItemComponents[i]['constructor'] as any); + } + } + const viewContainerRef = this.widgetHost.viewContainerRef; + viewContainerRef.clear(); + + const componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).item = this.item; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes["item"] && changes["item"].currentValue) { + this.loadComponent(); + } + } +} diff --git a/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.html b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.html new file mode 100644 index 0000000..0c255aa --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.html @@ -0,0 +1,15 @@ +
+
+
+
+ +

Cropfield

+

{{item.name}}

+
{{item.data.cropTypeName}}
+
{{item.data.startDate|date}} - {{item.data.endDate|date}}
+ +
+ + +
+
diff --git a/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.scss b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.scss new file mode 100644 index 0000000..8c27211 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.scss @@ -0,0 +1,25 @@ +@import "../../_theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.big-icon { + width: 100%; + color: white; + font-size: 9rem; + padding: 3rem; + text-align: center; +} + +.card-title { + font-size: 1rem; +} + +.spacer { + display: none; + height: 4rem; +} + +@media screen and (min-width:44rem) { + .spacer { + display: block; + } +} diff --git a/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.ts b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.ts new file mode 100644 index 0000000..770468c --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-cropfield/selected-item-cropfield.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Injectable, OnInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, Item, ItemService, FolderService, IListItem} from '@farmmaps/common'; +import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { AbstractSelectedItemComponent } from '../selected-item/selected-item.component'; +import { Observable } from 'rxjs'; + + +@ForItemType("vnd.farmmaps.itemtype.cropfield") +@Injectable() +@Component({ + selector: 'selected-item-cropfield', + templateUrl: './selected-item-cropfield.component.html', + styleUrls: ['./selected-item-cropfield.component.scss'] +}) +export class SelectedItemCropfieldComponent extends AbstractSelectedItemComponent implements OnInit{ + + public items: Observable; + + constructor(store: Store, itemTypeService: ItemTypeService, location: Location, router: Router, private itemService$: ItemService,private folderService$: FolderService) { + super(store, itemTypeService,location,router); + } + ngOnInit() { + this.items = this.folderService$.getItems(this.item.code, 0, 1000); + } +} diff --git a/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.html b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.html new file mode 100644 index 0000000..f3f2194 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.html @@ -0,0 +1,22 @@ +
+
+
+
+ +

{{item.name}}

+
+ + +
+ +
+
+
diff --git a/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.scss b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.scss new file mode 100644 index 0000000..ac67b50 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.scss @@ -0,0 +1,33 @@ +@import "../../_theme.scss"; +@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; + } +} diff --git a/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.ts b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.ts new file mode 100644 index 0000000..fe2e7cb --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-geotiff/selected-item-geotiff.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Injectable, OnInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, Item, ItemService, FolderService, IListItem} from '@farmmaps/common'; +import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { AbstractSelectedItemComponent } from '../selected-item/selected-item.component'; +import { Observable } from 'rxjs'; + + +@ForItemType("vnd.farmmaps.itemtype.geotiff.processed") +@Injectable() +@Component({ + selector: 'selected-item-geotiff', + templateUrl: './selected-item-geotiff.component.html', + styleUrls: ['./selected-item-geotiff.component.scss'] +}) +export class SelectedItemGeotiffComponent extends AbstractSelectedItemComponent { + + constructor(store: Store, 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; + } +} diff --git a/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.html b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.html new file mode 100644 index 0000000..e568793 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.html @@ -0,0 +1,22 @@ +
+
+
+
+ +

{{item.name}}

+
+
+ +
+ +
+ +
+
+
diff --git a/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.scss b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.scss new file mode 100644 index 0000000..9b251a3 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.scss @@ -0,0 +1,41 @@ +@import "../../_theme.scss"; +@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; +} + +.legend-container { + margin-top: 2rem; +} + +.legend-container select { + font-size: 1.5rem; +} + +@media screen and (min-width:44rem) { + .spacer { + display: block; + } +} diff --git a/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.ts b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.ts new file mode 100644 index 0000000..17fdc9d --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item-shape/selected-item-shape.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, Injectable, OnInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers, ItemTypeService, IItem, Item, ItemService, FolderService, IListItem} from '@farmmaps/common'; +import * as mapActions from '../../actions/map.actions'; +import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router'; +import { ForItemType } from '../for-item/for-itemtype.decorator'; +import { AbstractSelectedItemComponent } from '../selected-item/selected-item.component'; +import { Observable } from 'rxjs'; + + +@ForItemType("vnd.farmmaps.itemtype.shape.processed") +@Injectable() +@Component({ + selector: 'selected-item-shape', + templateUrl: './selected-item-shape.component.html', + styleUrls: ['./selected-item-shape.component.scss'] +}) +export class SelectedItemShapeComponent extends AbstractSelectedItemComponent { + + constructor(store: Store, 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)); + } +} diff --git a/projects/common-map/src/lib/components/selected-item/selected-item.component.html b/projects/common-map/src/lib/components/selected-item/selected-item.component.html new file mode 100644 index 0000000..3ccd4c8 --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item/selected-item.component.html @@ -0,0 +1,16 @@ +
+
+ +
+ +
+
+ +

{{item.name}}

+ View + Download + Edit + Add as overlay +
+
+
diff --git a/projects/common-map/src/lib/components/selected-item/selected-item.component.scss b/projects/common-map/src/lib/components/selected-item/selected-item.component.scss new file mode 100644 index 0000000..95925bb --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item/selected-item.component.scss @@ -0,0 +1,14 @@ +@import "../../theme.scss"; +@import "~bootstrap/scss/bootstrap.scss"; + +.big-icon { + width: 100%; + color: white; + font-size: 9rem; + padding: 3rem; + text-align: center; +} + +.card-title { + font-size: 1rem; +} diff --git a/projects/common-map/src/lib/components/selected-item/selected-item.component.ts b/projects/common-map/src/lib/components/selected-item/selected-item.component.ts new file mode 100644 index 0000000..4d8003d --- /dev/null +++ b/projects/common-map/src/lib/components/selected-item/selected-item.component.ts @@ -0,0 +1,56 @@ +import { Component, Input, Injectable } from '@angular/core'; +import { Location } from '@angular/common'; +import { Feature } from 'ol'; +import { Store } from '@ngrx/store'; +import * as mapReducers from '../../reducers/map.reducer'; +import { commonReducers,ItemTypeService, IItem, Item } from '@farmmaps/common'; +import * as mapActions from '../../actions/map.actions'; +import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router'; + + +@Injectable() +export abstract class AbstractSelectedItemComponent { + @Input() item: IItem + constructor(public store: Store, public itemTypeService: ItemTypeService, private location: Location, private router: Router) { + } + + handleOnView(item: IItem) { + var itemType = this.itemTypeService.itemTypes[item.itemType]; + if (itemType) { + if (itemType.viewer) { + let url = `/viewer/${itemType.viewer}/item/${item.code}`; + this.router.navigate([url]); + } + } + return false; + } + + handleOnEdit(item: IItem) { + let url = `/editor/property/item/${item.code}`; + this.router.navigate([url]); + return false; + } + + handleAddAsLayer(item: IItem,layerIndex:number = -1) { + this.store.dispatch(new mapActions.AddLayer(item,layerIndex)); + return false; + } + + handleBackToList(event: MouseEvent) { + event.preventDefault(); + this.location.back(); + } +} + +@Injectable() +@Component({ + selector: 'selected-item', + templateUrl: './selected-item.component.html', + styleUrls: ['./selected-item.component.scss'] +}) +export class SelectedItemComponent extends AbstractSelectedItemComponent { + + constructor(store: Store, itemTypeService: ItemTypeService, location: Location, router: Router) { + super(store, itemTypeService,location,router); + } +} diff --git a/projects/common-map/src/lib/components/widget-host/widget-host.directive.ts b/projects/common-map/src/lib/components/widget-host/widget-host.directive.ts new file mode 100644 index 0000000..455a135 --- /dev/null +++ b/projects/common-map/src/lib/components/widget-host/widget-host.directive.ts @@ -0,0 +1,8 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[widget-host]', +}) +export class WidgetHostDirective { + constructor(public viewContainerRef: ViewContainerRef) { } +} diff --git a/projects/common-map/src/lib/components/widget-status/widget-status.component.css b/projects/common-map/src/lib/components/widget-status/widget-status.component.css new file mode 100644 index 0000000..e69de29 diff --git a/projects/common-map/src/lib/components/widget-status/widget-status.component.html b/projects/common-map/src/lib/components/widget-status/widget-status.component.html new file mode 100644 index 0000000..5f2c3f3 --- /dev/null +++ b/projects/common-map/src/lib/components/widget-status/widget-status.component.html @@ -0,0 +1,6 @@ +
+
Stage:Pre-alpha
+
Stage:Alpha
+
Stage:Beta
+
Stage:RC
+
diff --git a/projects/common-map/src/lib/components/widget-status/widget-status.component.ts b/projects/common-map/src/lib/components/widget-status/widget-status.component.ts new file mode 100644 index 0000000..b15f029 --- /dev/null +++ b/projects/common-map/src/lib/components/widget-status/widget-status.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'widget-status', + templateUrl: './widget-status.component.html', + styleUrls: ['./widget-status.component.css'] +}) +export class WidgetStatusComponent implements OnInit { + @Input() stage: Stage; + @Input() info: string; + StageEnum = Stage; + + constructor() { } + + ngOnInit() { } + +} +export enum Stage { + DevelopmentPreAlpha, + DevelopmentAlpha, + DevelopmentBeta, + ReleaseCandidate, + Final +} diff --git a/projects/common-map/src/lib/effects/map.effects.ts b/projects/common-map/src/lib/effects/map.effects.ts new file mode 100644 index 0000000..5f45ea4 --- /dev/null +++ b/projects/common-map/src/lib/effects/map.effects.ts @@ -0,0 +1,230 @@ +import { Injectable, Inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Store, Action } from '@ngrx/store'; +import { Effect, Actions,ofType } from '@ngrx/effects'; + +import { Observable , of } from 'rxjs'; +import { withLatestFrom, switchMap, map, catchError, mergeMap, delay} from 'rxjs/operators'; + +import {GeoJSON} from 'ol/format'; +import {Feature} from 'ol'; +import { getCenter, Extent, createEmpty, extend} from 'ol/extent'; +import {Point} 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 { IListItem, IItem } from '@farmmaps/common'; +import { FolderService, ItemService } from '@farmmaps/common'; +import { tassign } from 'tassign'; + +@Injectable() +export class MapEffects { + private _format: GeoJSON; + + private toPointFeature(feature: any): Feature { + var f = this._format.readFeature(feature); + var centroid = getCenter(f.getGeometry().getExtent()); + f.setGeometry(new Point(centroid)); + return f; + } + + @Effect() + init$: Observable = this.actions$.pipe( + ofType(mapActions.INIT), + withLatestFrom(this.store$.select(commonReducers.selectGetRootItems)), + switchMap(([action, rootItems]) => { + for (let rootItem of rootItems) { + if (rootItem.itemType == "UPLOADS_FOLDER") return of(new mapActions.SetParent(rootItem.code)); + } + return []; + } + )); + + @Effect() + initBaseLayers$: Observable = this.actions$.pipe( + ofType(mapActions.INIT), + withLatestFrom(this.store$.select(mapReducers.selectGetProjection)), + map(([action, projection]) => new mapActions.LoadBaseLayers(projection))); + + @Effect() + loadBaseLayers$: Observable = 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)))); + })); + + @Effect() + initRootItems$: Observable = this.actions$.pipe( + ofType(commonActions.INITROOTSUCCESS), + map((action) => new mapActions.Init() + )); + + @Effect() + startSearch$: Observable = this.actions$.pipe( + ofType(mapActions.STARTSEARCH), + switchMap((action: mapActions.StartSearch) => { + console.log("Start search"); + var startDate = action.queryState.startDate; + var endDate = action.queryState.endDate; + var newAction; + if (action.queryState.itemCode || action.queryState.parentCode || action.queryState.itemType || action.queryState.query || action.queryState.tags) { + newAction= this.itemService$.getFeatures(action.queryState.bbox, "EPSG:3857", action.queryState.query, action.queryState.tags, startDate, endDate, action.queryState.itemType, action.queryState.parentCode).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._format.readFeatures(features), action.queryState)); + } + ), + catchError(error => of(new commonActions.Fail(error)))); + } else { + newAction= of(new commonActions.Escape(true,false)); + } + return newAction; + })); + + + @Effect() + startSearchSucces$: Observable = this.actions$.pipe( + ofType(mapActions.STARTSEARCHSUCCESS), + mergeMap((action: mapActions.StartSearchSuccess) => { + if (action.query.bboxFilter) { + return []; + } else { + var extent = createEmpty(); + + if (extent) { + for (let f of action.features) { + extend(extent, (f as Feature).getGeometry().getExtent()); + } + } + //return []; + return of(new mapActions.SetExtent(extent)); + } + })); + + @Effect() + selectItem$: Observable = 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( + map((item: IItem) => new mapActions.SelectItemSuccess(item)), + catchError(error => of(new commonActions.Fail(error)))) + } else { + return []; + } + } + )); + + @Effect() + selectItemSuccess$: Observable = this.actions$.pipe( + ofType(mapActions.SELECTITEMSUCCESS), + switchMap((action:mapActions.SelectItemSuccess) => { + return this.itemService$.getFeature(action.item.code, "EPSG:3857").pipe( + map((feature: IItem) => new mapActions.AddFeatureSuccess(this._format.readFeature(feature) )), + catchError(error => of(new commonActions.Fail(error)))); + } + )); + + @Effect() + uploadedItemClick$: Observable = this.actions$.pipe( + ofType(commonActions.UPLOADEDFILECLICK), + switchMap((action: commonActions.UploadedFileClick) => of(new mapActions.DoQuery(tassign(mapReducers.initialState.query, {itemCode:action.itemCode}))) + )); + + //@Effect() + //itemAdded$: Observable = this.actions$.pipe( + // ofType(commonActions.ITEMADDEDEVENT), + // withLatestFrom(this.store$.select(mapReducers.selectGetParentCode)), + // mergeMap(([action, parentCode]) => { + // let itemAddedAction = action as commonActions.ItemAddedEvent; + // if (parentCode && itemAddedAction.attributes["parentCode"] == parentCode) { + // return this.itemService$.getFeature(itemAddedAction.itemCode,"EPSG:3857").pipe( + // map((feature: Feature) => new mapActions.AddFeatureSuccess(this.toPointFeature(feature))), + // catchError(error => of(new commonActions.Fail(error)))) + // } else + // return [ + // ]; + // })); + + @Effect() + featureUpdate$: Observable = this.actions$.pipe( + ofType(commonActions.ITEMCHANGEDEVENT), + withLatestFrom(this.store$.select(mapReducers.selectGetFeatures)), + mergeMap(([action, features]) => { + let itemChangedAction = action as commonActions.ItemChangedEvent; + var feature: Feature = null; + for (let f of features) { + if (f.get("code") == itemChangedAction.itemCode) { + feature = f; + break; + } + } + if (feature) { + return this.itemService$.getFeature(itemChangedAction.itemCode, "EPSG:3857").pipe( + map((feature: any) => new mapActions.UpdateFeatureSuccess(this.toPointFeature(feature))), + catchError(error => of(new commonActions.Fail(error)))); + } else { + return []; + } + })); + + @Effect() + itemUpdate$: Observable = 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( + map((item: IItem) => new mapActions.SelectItemSuccess(item)), + catchError(error => of(new commonActions.Fail(error)))); + } else { + return []; + } + })); + + @Effect() + setQueryState$: Observable = this.actions$.pipe( + ofType(mapActions.SETQUERYSTATE), + switchMap((action: mapActions.SetQueryState) => { + var newAction:Action; + if (action.queryState.itemCode && action.queryState.itemCode != "") { + newAction= new mapActions.SelectItem(action.queryState.itemCode); + } else { + newAction= new mapActions.StartSearch(action.queryState); + } + return of(newAction); + })); + + @Effect() + setState$: Observable = this.actions$.pipe( + ofType(mapActions.SETSTATE), + switchMap((action: mapActions.SetState) => { + var newAction:Action; + if (action.queryState.itemCode && action.queryState.itemCode != "") { + newAction= new mapActions.SelectItem(action.queryState.itemCode); + } else { + newAction= new mapActions.StartSearch(action.queryState); + } + return of(newAction); + })); + + constructor(private actions$: Actions, private store$: Store, private folderService$: FolderService, private itemService$: ItemService) { + this._format = new GeoJSON(); + } +} diff --git a/projects/common-map/src/lib/models/color.map.js.map b/projects/common-map/src/lib/models/color.map.js.map new file mode 100644 index 0000000..5dbe652 --- /dev/null +++ b/projects/common-map/src/lib/models/color.map.js.map @@ -0,0 +1 @@ +{"version":3,"file":"color.map.js","sourceRoot":"","sources":["color.map.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/projects/common-map/src/lib/models/color.map.ts b/projects/common-map/src/lib/models/color.map.ts new file mode 100644 index 0000000..2a8828c --- /dev/null +++ b/projects/common-map/src/lib/models/color.map.ts @@ -0,0 +1,69 @@ +export interface IColor { + red: number, + green: number, + blue: number, + alpha: number, +} + +export interface IColorEntry { + value: number, + color: IColor +} + +export interface IHistogramEntry { + value: number, + freqency: number +} + +export interface IHistogram { + min: number, + max: number, + mean: number, + stddev: number, + classes: number, + entries: IHistogramEntry[] +} + +export interface IGradientstop { + relativestop: number, + color: IColor +} + +export interface IColorMap { + gradient: IGradientstop[], + noValue: IColorEntry, + entries: IColorEntry[] +} + +export interface IBand { + histogram: IHistogram +} + + +export interface IRenderer { + band:IBand, + colorMap: IColorMap +} + +export interface IRenderoutput { + renderoutputType: string +} + +export interface IRenderoutputTiles { + renderoutputType: string, + minzoom: number, + maxzoom: number +} + +export interface IRenderoutputImage { + renderoutputType: string, + extent: [number,number,number,number] +} + +export interface ILayer { + name: string, + unit: string, + index: number, + renderer: IRenderer, + rendering: IRenderoutput +} diff --git a/projects/common-map/src/lib/models/index.js.map b/projects/common-map/src/lib/models/index.js.map new file mode 100644 index 0000000..04a7089 --- /dev/null +++ b/projects/common-map/src/lib/models/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAEA,kCAA4B"} \ No newline at end of file diff --git a/projects/common-map/src/lib/models/index.ts b/projects/common-map/src/lib/models/index.ts new file mode 100644 index 0000000..61012f1 --- /dev/null +++ b/projects/common-map/src/lib/models/index.ts @@ -0,0 +1,7 @@ +export * from './map.state'; +export * from './selected.features'; +export * from './item.layer' +export * from './layer.data' +export * from './color.map'; +export * from './query.state'; +export * from './period.state'; diff --git a/projects/common-map/src/lib/models/item.layer.js.map b/projects/common-map/src/lib/models/item.layer.js.map new file mode 100644 index 0000000..38b034f --- /dev/null +++ b/projects/common-map/src/lib/models/item.layer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"item.layer.js","sourceRoot":"","sources":["item.layer.ts"],"names":[],"mappings":";;AAaA;IASE,mBAAY,IAAU;QAPf,UAAK,GAAU,IAAI,CAAC;QACpB,YAAO,GAAY,IAAI,CAAC;QACxB,kBAAa,GAAY,KAAK,CAAC;QAE/B,YAAO,GAAW,CAAC,CAAC;QACpB,eAAU,GAAW,CAAC,CAAC,CAAC;QAG7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IACH,gBAAC;AAAD,CAAC,AAZD,IAYC;AAZY,8BAAS"} \ No newline at end of file diff --git a/projects/common-map/src/lib/models/item.layer.ts b/projects/common-map/src/lib/models/item.layer.ts new file mode 100644 index 0000000..7ddf00a --- /dev/null +++ b/projects/common-map/src/lib/models/item.layer.ts @@ -0,0 +1,26 @@ +import { IItem } from '@farmmaps/common'; +import {Layer} from 'ol/layer'; + +export interface IItemLayer { + item: IItem, + layer: Layer, + visible: boolean, + legendVisible:boolean, + projection: string, + opacity: number, + layerIndex:number +} + +export class ItemLayer implements IItemLayer { + public item: IItem; + public layer: Layer = null; + public visible: boolean = true; + public legendVisible: boolean = false; + public projection: string; + public opacity: number = 1; + public layerIndex: number = -1; + + constructor(item:IItem) { + this.item = item; + } +} diff --git a/projects/common-map/src/lib/models/layer.data.ts b/projects/common-map/src/lib/models/layer.data.ts new file mode 100644 index 0000000..9b4016a --- /dev/null +++ b/projects/common-map/src/lib/models/layer.data.ts @@ -0,0 +1,6 @@ +export interface ILayerData { + interfaceType:string, + projection: string, + options: any, + url:string +} diff --git a/projects/common-map/src/lib/models/map.state.ts b/projects/common-map/src/lib/models/map.state.ts new file mode 100644 index 0000000..f80ce69 --- /dev/null +++ b/projects/common-map/src/lib/models/map.state.ts @@ -0,0 +1,7 @@ +export interface IMapState { + zoom: number; + rotation: number; + xCenter: number; + yCenter: number; + baseLayerCode: string; +} diff --git a/projects/common-map/src/lib/models/period.state.ts b/projects/common-map/src/lib/models/period.state.ts new file mode 100644 index 0000000..65a7429 --- /dev/null +++ b/projects/common-map/src/lib/models/period.state.ts @@ -0,0 +1,4 @@ +export interface IPeriodState { + startDate: Date, + endDate:Date +} diff --git a/projects/common-map/src/lib/models/query.state.ts b/projects/common-map/src/lib/models/query.state.ts new file mode 100644 index 0000000..cc011cd --- /dev/null +++ b/projects/common-map/src/lib/models/query.state.ts @@ -0,0 +1,12 @@ +export interface IQueryState { + itemCode: string; + parentCode: string; + level: number; + itemType: string; + query: string; + tags: string; + bboxFilter: boolean; + startDate: Date; + endDate: Date; + bbox: number[]; +} diff --git a/projects/common-map/src/lib/models/selected.features.ts b/projects/common-map/src/lib/models/selected.features.ts new file mode 100644 index 0000000..494cd73 --- /dev/null +++ b/projects/common-map/src/lib/models/selected.features.ts @@ -0,0 +1,5 @@ +export interface ISelectedFeatures { + x: number; + y: number; + features: any[]; +} diff --git a/projects/common-map/src/lib/module-name.ts b/projects/common-map/src/lib/module-name.ts new file mode 100644 index 0000000..59eec1f --- /dev/null +++ b/projects/common-map/src/lib/module-name.ts @@ -0,0 +1 @@ +export const MODULE_NAME = "FarmMapsCommonMap"; diff --git a/projects/common-map/src/lib/reducers/map.reducer.ts b/projects/common-map/src/lib/reducers/map.reducer.ts new file mode 100644 index 0000000..8cfc528 --- /dev/null +++ b/projects/common-map/src/lib/reducers/map.reducer.ts @@ -0,0 +1,369 @@ +import { tassign } from 'tassign'; +import { IItem,Item } from '@farmmaps/common'; +import { IItemLayer,ItemLayer, IMapState,IQueryState,IPeriodState} from '../models' +import * as mapActions from '../actions/map.actions'; +import {commonActions} from '@farmmaps/common'; +import { createSelector, createFeatureSelector } from '@ngrx/store'; + +import {Feature} from 'ol'; + +import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store'; + +import { MODULE_NAME } from '../module-name'; + +const startDate:Date = new Date(new Date(Date.now()).getFullYear(), new Date(Date.now()).getMonth() - 3, 1); +const endDate:Date = new Date(Date.now()); + +export const initialQueryState: IQueryState = { + itemCode: null, + parentCode: null, + level: 1, + itemType: null, + bboxFilter: false, + query: null, + tags: null, + endDate: null, + startDate: null, + bbox: [] +}; + +export interface State { + period:IPeriodState, + mapState: IMapState, + viewExtent: number[], + queryState: IQueryState, + query:IQueryState, + parentCode: string, + features: Array, + panelVisible: boolean, + panelCollapsed: boolean, + selectedFeature: Feature, + selectedItem:IItem, + clearEnabled: boolean, + searchCollapsed: boolean, + searchMinified: boolean, + menuVisible: boolean, + extent: number[], + baseLayers: Array + overlayLayers: Array, + selectedItemLayer: IItemLayer, + projection: string, + selectedBaseLayer: IItemLayer, + selectedOverlayLayer: IItemLayer +} + +export const initialState: State = { + period: { + startDate: startDate, + endDate: endDate + }, + mapState: { + zoom: 8, + rotation: 0, + xCenter: 5.377554, + yCenter: 52.162422, + baseLayerCode: "" + }, + viewExtent:[], + queryState: tassign(initialQueryState), + query: tassign(initialQueryState), + parentCode: null, + features: [], + panelVisible: false, + panelCollapsed: false, + selectedFeature: null, + selectedItem: null, + clearEnabled: false, + searchCollapsed: true, + searchMinified:false, + menuVisible: true, + extent: null, + baseLayers: [], + overlayLayers: [], + projection: "EPSG:3857", + selectedBaseLayer: null, + selectedOverlayLayer: null, + selectedItemLayer: null +} + +export function reducer(state = initialState, action: mapActions.Actions | commonActions.Actions | RouterNavigationAction): State { + switch (action.type) { + case ROUTER_NAVIGATION: { + let a = action as RouterNavigationAction; + return tassign(state); + } + case mapActions.SETMAPSTATE: { + let a = action as mapActions.SetMapState; + return tassign(state, { + mapState: a.mapState + }); + } + case mapActions.SETQUERYSTATE: { + let a = action as mapActions.SetQueryState; + return tassign(state, { queryState: tassign(a.queryState )}); + } + case mapActions.SETSTATE: { + let a = action as mapActions.SetState; + return tassign(state, { mapState: tassign(a.mapState), queryState: tassign(a.queryState)}); + } + case mapActions.SETVIEWEXTENT: { + let a = action as mapActions.SetViewExtent; + return tassign(state, { viewExtent: a.extent }); + } + case mapActions.SETPARENT: { + let a = action as mapActions.SetParent; + return tassign(state, { + parentCode : a.parentCode + }); + } + case mapActions.STARTSEARCHSUCCESS: { + let a = action as mapActions.StartSearchSuccess; + return tassign(state, { + panelVisible: true, + clearEnabled: true, + searchMinified: true, + features: a.features + }); + } + case mapActions.SELECTFEATURE: { + let a = action as mapActions.SelectFeature; + return tassign(state, { + selectedFeature: state.selectedItem?state.selectedFeature: a.feature + }); + } + case mapActions.SELECTITEM: { + return tassign(state, { + selectedItem: null, + searchMinified:false, + selectedItemLayer: null, + features:[] + }); + } + case mapActions.SELECTITEMSUCCESS: { + let a = action as mapActions.SelectItemSuccess; + 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 ) { + itemLayer = new ItemLayer(a.item); + } + return tassign(state, { + selectedItem: a.item, + selectedItemLayer: itemLayer, + panelVisible: a.item != null, + clearEnabled: a.item != null, + searchCollapsed: false, + searchMinified: true, + queryState: tassign(state.queryState, {itemCode:a.item ? a.item.code:null}) + }); + } + case mapActions.STARTSEARCH: { + let a = action as mapActions.StartSearch; + return tassign(state, { + selectedItem: null, + selectedItemLayer:null, + queryState: tassign(a.queryState), + searchCollapsed: false, + searchMinified: true, + menuVisible:false + }); + } + case mapActions.DOQUERY: { + let a = action as mapActions.DoQuery; + return tassign(state, { + query: tassign(a.query, { bbox: a.query.bboxFilter ? state.viewExtent : [] })}); + } + case mapActions.ADDFEATURESUCCESS: { + let a = action as mapActions.AddFeatureSuccess; + let features = state.features.slice(); + features.push(a.feature); + return tassign(state, { + panelVisible: true, + selectedFeature: a.feature, + menuVisible: false, + extent: a.feature.getGeometry().getExtent(), + searchCollapsed: false, + clearEnabled:true, + features:features + }); + } + case mapActions.UPDATEFEATURESUCCESS: { + let a = action as mapActions.UpdateFeatureSuccess; + let features: any[] = []; + var index = -1; + for (var i = 0; i < state.features.length; i++) { + if (state.features[i].get("code") == a.feature.get("code")) { + features.push(a.feature); + } else { + features.push(state.features[i]); + } + } + return tassign(state, { features: features }); + } + case mapActions.EXPANDSEARCH: { + return tassign(state, { searchCollapsed: false }); + } + case mapActions.COLLAPSESEARCH: { + return tassign(state, { searchCollapsed: state.panelVisible ? false: true}); + } + case mapActions.TOGGLEMENU: { + return tassign(state, { menuVisible: !state.menuVisible }); + } + case mapActions.SETEXTENT: { + let a = action as mapActions.SetExtent; + return tassign(state, { extent: a.extent }); + } + case mapActions.ADDLAYER: { + let a = action as mapActions.AddLayer; + let itemLayers = state.overlayLayers.slice(0); + let itemLayer = new ItemLayer(a.item); + itemLayer.layerIndex = a.layerIndex == -1 ? 0 : a.layerIndex; + itemLayers.push(itemLayer); + return tassign(state, { overlayLayers: itemLayers, selectedOverlayLayer: itemLayer }); + } + case mapActions.REMOVELAYER: { + let a = action as mapActions.RemoveLayer; + let newLayers = state.overlayLayers.slice(0); + let i = state.overlayLayers.indexOf(a.itemLayer); + var selectedOverlayLayer: IItemLayer = null; + if (i>0 && state.overlayLayers.length > 1) + selectedOverlayLayer = state.overlayLayers[i - 1]; + else if (i == 0 && state.overlayLayers.length > 1) + selectedOverlayLayer = state.overlayLayers[i + 1]; + newLayers.splice(i, 1); + return tassign(state, { overlayLayers: newLayers, selectedOverlayLayer: selectedOverlayLayer }); + } + case mapActions.SETVISIBILITY: { + let a = action as mapActions.SetVisibility; + let newLayers = state.overlayLayers.slice(0); + let i = state.overlayLayers.indexOf(a.itemLayer); + newLayers[i].visible = a.visibility; + return tassign(state, { overlayLayers: newLayers }); + } + case mapActions.SETOPACITY: { + let a = action as mapActions.SetOpacity; + let newLayers = state.overlayLayers.slice(0); + let i = state.overlayLayers.indexOf(a.itemLayer); + newLayers[i].opacity = a.opacity; + return tassign(state, { overlayLayers: newLayers }); + } + case mapActions.SETLAYERINDEX: { + let a = action as mapActions.SetLayerIndex; + if (a.itemLayer == null) { + var newItemlayer = new ItemLayer(state.selectedItemLayer.item); + newItemlayer.layerIndex = a.layerIndex; + return tassign(state, { selectedItemLayer: newItemlayer}) + } else { + let newLayers = state.overlayLayers.slice(0); + let i = state.overlayLayers.indexOf(a.itemLayer); + newLayers[i].layerIndex = a.layerIndex; + return tassign(state, { overlayLayers: newLayers }); + } + } + case mapActions.LOADBASELAYERSSUCCESS: { + let a =action as mapActions.LoadBaseLayersSuccess; + let baseLayers:ItemLayer[] = []; + for (let item of a.items) { + var l = new ItemLayer(item); + l.visible = false; + baseLayers.push(l); + } + var selectedBaseLayer: IItemLayer = null; + var mapState = tassign(state.mapState); + console.log(`Base layerload: ${mapState.baseLayerCode}`) + if (baseLayers.length > 0 && mapState.baseLayerCode != "") { + selectedBaseLayer = baseLayers.filter(layer => layer.item.code === mapState.baseLayerCode)[0]; + selectedBaseLayer.visible = true; + } else if (baseLayers.length > 0) { + selectedBaseLayer = baseLayers[0]; + selectedBaseLayer.visible = true; + mapState.baseLayerCode = selectedBaseLayer.item.code; + } + return tassign(state, { mapState:mapState, baseLayers: baseLayers, selectedBaseLayer: selectedBaseLayer }); + } + case mapActions.SELECTBASELAYER: { + let a = action as mapActions.SelectBaseLayer; + let baseLayers = state.baseLayers.slice(0); + baseLayers.forEach((l) => l.visible = false); + let i = state.baseLayers.indexOf(a.itemLayer); + baseLayers[i].visible = true; + var mapState = tassign(state.mapState); + mapState.baseLayerCode = a.itemLayer.item.code; + return tassign(state, {mapState:mapState, baseLayers:baseLayers,selectedBaseLayer:a.itemLayer }); + } + case mapActions.SELECTOVERLAYLAYER: { + let a = action as mapActions.SelectOverlayLayer; + return tassign(state, { selectedOverlayLayer: a.itemLayer }); + } + case commonActions.ESCAPE: { + let a = action as commonActions.Escape; + let newQueryState = tassign(state.queryState, { query: null, tags: null, itemCode: null, parentCode: null, itemType: null }); + if (a.escapeKey) { + return tassign(state, { + panelVisible: false, + panelCollapsed:false, + selectedItem: null, + selectedItemLayer: null, + selectedFeature: null, + queryState: newQueryState, + clearEnabled: false, + searchCollapsed: true, + searchMinified: false, + menuVisible:false, + features: [], + query:initialState.query + }); + } else { + return tassign(state, {}); + } + } + default: { + return state; + } + } +} + +export const getMapState = (state: State) => state.mapState; +export const getParentCode = (state: State) => state.parentCode; +export const getFeatures = (state: State) => state.features; +export const getPanelVisible = (state: State) => state.panelVisible; +export const getPanelCollapsed = (state: State) => state.panelCollapsed; +export const getSelectedFeature = (state: State) => state.selectedFeature; +export const getSelectedItem = (state: State) => state.selectedItem; +export const getQueryState = (state: State) => state.queryState; +export const getClearEnabled = (state: State) => state.clearEnabled; +export const getSearchCollapsed = (state: State) => state.searchCollapsed; +export const getSearchMinified = (state: State) => state.searchMinified; +export const getMenuVisible = (state: State) => state.menuVisible; +export const getExtent = (state: State) => state.extent; +export const getOverlayLayers = (state: State) => state.overlayLayers; +export const getBaseLayers = (state: State) => state.baseLayers; +export const getProjection = (state: State) => state.projection; +export const getSelectedBaseLayer = (state: State) => state.selectedBaseLayer; +export const getSelectedOverlayLayer = (state: State) => state.selectedOverlayLayer; +export const getQuery = (state: State) => state.query; +export const getSelectedItemLayer = (state: State) => state.selectedItemLayer; +export const getPeriod = (state:State) => state.period; + +export const selectMapState = createFeatureSelector(MODULE_NAME); +export const selectGetMapState= createSelector(selectMapState, getMapState); +export const selectGetParentCode = createSelector(selectMapState, getParentCode); +export const selectGetFeatures = createSelector(selectMapState, getFeatures); +export const selectGetPanelVisible = createSelector(selectMapState, getPanelVisible); +export const selectGetPanelCollapsed = createSelector(selectMapState, getPanelCollapsed); +export const selectGetSelectedFeature = createSelector(selectMapState, getSelectedFeature); +export const selectGetSelectedItem = createSelector(selectMapState, getSelectedItem); +export const selectGetQueryState = createSelector(selectMapState, getQueryState); +export const selectGetClearEnabled = createSelector(selectMapState, getClearEnabled); +export const selectGetSearchCollapsed = createSelector(selectMapState, getSearchCollapsed); +export const selectGetSearchMinified = createSelector(selectMapState, getSearchMinified); +export const selectGetMenuVisible = createSelector(selectMapState, getMenuVisible); +export const selectGetExtent = createSelector(selectMapState, getExtent); +export const selectGetOverlayLayers = createSelector(selectMapState, getOverlayLayers); +export const selectGetBaseLayers = createSelector(selectMapState, getBaseLayers); +export const selectGetProjection = createSelector(selectMapState, getProjection); +export const selectGetSelectedBaseLayer = createSelector(selectMapState, getSelectedBaseLayer); +export const selectGetSelectedOverlayLayer = createSelector(selectMapState, getSelectedOverlayLayer); +export const selectGetQuery = createSelector(selectMapState, getQuery); +export const selectGetSelectedItemLayer = createSelector(selectMapState, getSelectedItemLayer); +export const selectGetPeriod = createSelector(selectMapState, getPeriod); + + diff --git a/projects/common-map/src/lib/services/geolocation.service.ts b/projects/common-map/src/lib/services/geolocation.service.ts new file mode 100644 index 0000000..973e590 --- /dev/null +++ b/projects/common-map/src/lib/services/geolocation.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Observer, Observable } from 'rxjs'; + +/** + * GeolocationService class. + * https://developers.google.com/maps/documentation/javascript/ + * https://dev.w3.org/geo/api/spec-source.html + */ +@Injectable() +export class GeolocationService { + + /** + * Tries HTML5 geolocation. + * + * Wraps the Geolocation API into an observable. + * + * @return An observable of Position + */ + getCurrentPosition(): Observable { + return Observable.create((observer: Observer) => { + // Invokes getCurrentPosition method of Geolocation API. + navigator.geolocation.watchPosition( + (position: Position) => { + observer.next(position); + observer.complete(); + }, + (error: PositionError) => { + console.log('Geolocation service: ' + error.message); + observer.error(error); + }, + { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + } + ); + }); + } + +} diff --git a/projects/common-map/src/lib/services/state-serializer.service.ts b/projects/common-map/src/lib/services/state-serializer.service.ts new file mode 100644 index 0000000..3fb9b31 --- /dev/null +++ b/projects/common-map/src/lib/services/state-serializer.service.ts @@ -0,0 +1,52 @@ +import { Injectable, Query } from '@angular/core'; +import { IQueryState } from '../models/query.state'; +import { query } from '@angular/animations'; + +@Injectable() +export class StateSerializerService { + + serialize(queryState: IQueryState): string { + + var state = ""; + state += (queryState.itemCode) ? queryState.itemCode : ""; + state += ";"; + state += (queryState.parentCode) ? queryState.parentCode + ";" + queryState.level.toString() : ";"; + state += ";"; + state += (queryState.itemType) ? queryState.itemType : ""; + state += ";"; + state += (queryState.query) ? encodeURIComponent( queryState.query ) : ""; + state += ";"; + state += (queryState.tags) ? encodeURIComponent( queryState.tags ) : ""; + state += ";"; + state += (queryState.bboxFilter) ? "1" : "0"; + state += ";"; + state += (queryState.startDate) ? JSON.stringify(queryState.startDate) : ""; + state += ";"; + state += (queryState.endDate) ? JSON.stringify(queryState.endDate) : ""; + state += ";"; + state += (queryState.bboxFilter && queryState.bbox && queryState.bbox.length>0) ? JSON.stringify(queryState.bbox) : ""; + + return btoa(state); + } + + deserialize(queryState: string): IQueryState { + var state: IQueryState = { itemCode: null, parentCode: null, level: 1, itemType: null, query: null, tags: null, bboxFilter: false, startDate: null, endDate: null,bbox:[] }; + var stateParts = atob(queryState).split(";"); + if (stateParts.length == 10) { + if (stateParts[0] != "") state.itemCode = stateParts[0]; + if (stateParts[1] != "") { + state.parentCode = stateParts[1]; + state.level = parseInt(stateParts[2]); + } + if (stateParts[3] != "") state.itemType = stateParts[3]; + if (stateParts[4] != "") state.query = decodeURIComponent(stateParts[4]); + if (stateParts[5] != "") state.tags = decodeURIComponent( stateParts[5]); + if (stateParts[6] != "") state.bboxFilter = parseInt(stateParts[6]) == 1; + if (stateParts[7] != "") state.startDate = new Date(Date.parse(JSON.parse(stateParts[7]))); + if (stateParts[8] != "") state.endDate = new Date(Date.parse(JSON.parse(stateParts[8]))); + if (stateParts[9] != "") state.bbox = JSON.parse(stateParts[9]); + } + + return state; + } +} diff --git a/projects/common-map/src/lib/t/tt/common-map-routing.module.ts b/projects/common-map/src/lib/t/tt/common-map-routing.module.ts new file mode 100644 index 0000000..259a921 --- /dev/null +++ b/projects/common-map/src/lib/t/tt/common-map-routing.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { MapComponent } from './map.component'; +import { AuthGuard } from '@farmmaps/common'; + +const routes = [ + { + path: '', canActivateChild: [AuthGuard], children: [ + { + path: '', + component: MapComponent + } + ] + }, + { + path: ':xCenter/:yCenter/:zoom/:rotation/:baseLayer/:queryState', canActivateChild: [AuthGuard], children: [ + { + path: '', + component: MapComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class MapRoutingModule { } diff --git a/projects/common-map/src/lib/t/tt/common-map.module.ts b/projects/common-map/src/lib/t/tt/common-map.module.ts new file mode 100644 index 0000000..40dc5f9 --- /dev/null +++ b/projects/common-map/src/lib/t/tt/common-map.module.ts @@ -0,0 +1,190 @@ +//angular +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +//external modules +import { AngularOpenlayersModule } from 'ngx-openlayers'; +import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; + +//common modules +import { AppCommonModule } from '@farmmaps/common'; + +import { MODULE_NAME } from './module-name'; +import { reducer,initialState } from './reducers/map.reducer'; +import * as mapEffects from './effects'; + +// components +import { GpsLocation} from './components/aol/gps-location/gps-location.component'; +//import {Switch2D3DComponent } from './components/aol/switch2d3d/switch2d3d.component'; +import {FeatureListFeatureCropfieldComponent } from './components/feature-list-feature-cropfield/feature-list-feature-cropfield.component'; +import { FeatureListFeatureCroppingschemeComponent} from './components/feature-list-feature-croppingscheme/feature-list-feature-croppingscheme.component'; +import { ItemWidgetListComponent} from './components/item-widget-list/item-widget-list.component'; +import { AbstractItemListItemComponent, ItemListItemComponent, AbstractItemWidgetComponent } from './components/item-list-item/item-list-item.component'; +import { ItemListItemContainerComponent } from './components/item-list-item-container/item-list-item-container.component'; +import { AbstractItemListComponent,ItemListComponent} from './components/item-list/item-list.component'; +import { AbstractSelectedItemComponent, SelectedItemComponent } from './components/selected-item/selected-item.component'; +import { SelectedItemCropfieldComponent } from './components/selected-item-cropfield/selected-item-cropfield.component'; +import { SelectedItemGeotiffComponent } from './components/selected-item-geotiff/selected-item-geotiff.component'; +import {SelectedItemShapeComponent } from './components/selected-item-shape/selected-item-shape.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 {FeatureListFeatureContainerComponent } from './components/feature-list-feature-container/feature-list-feature-container.component'; +import { FeatureListCroppingschemeComponent } from './components/feature-list-croppingscheme/feature-list-croppingscheme.component'; +import {FeatureListCropfieldComponent } from './components/feature-list-cropfield/feature-list-cropfield.component'; +import {FeatureListContainerComponent } from './components/feature-list-container/feature-list-container.component'; +import { WidgetHostDirective} from './components/widget-host/widget-host.directive'; +import { FeatureListComponent,AbstractFeatureListComponent} from './components/feature-list/feature-list.component'; +import { FileDropTargetComponent } from './components/aol/file-drop-target/file-drop-target.component'; +import { ItemVectorSourceComponent } from './components/aol/item-vector-source/item-vector-source.component'; +import { ItemFeaturesSourceComponent } from './components/aol/item-features-source/item-features-source.component'; +import { ItemLayersComponent } from './components/aol/item-layers/item-layers.component'; +import { ZoomToExtentComponent } from './components/aol/zoom-to-extent/zoom-to-extent.component'; +import { RotationResetComponent } from './components/aol/rotation-reset/rotation-reset.component'; +import { LayerListComponent } from './components/aol/layer-list/layer-list.component'; +import { MetaDataModalComponent } from './components/meta-data-modal/meta-data-modal.component'; +import { SelectPeriodModalComponent } from './components/select-period-modal/select-period-modal.component'; +import { MapComponent } from './map.component'; +import { MapSearchComponent } from './components/map-search'; +import { MapRoutingModule } from './common-map-routing.module'; +import { LegendComponent } from './components/legend'; +import { LayerVectorImageComponent } from './components/aol/layer-vector-image/layer-vector-image.component'; +import { StateSerializerService } from './services/state-serializer.service'; +import { GeolocationService } from './services/geolocation.service'; +import { localStorageSync } from 'ngrx-store-localstorage'; +import { ItemWidgetWeatherComponent } from './components/item-widget-weather/item-widget-weather.component'; +import { ItemListItemTemporalComponent } from './components/item-list-item-temporal/item-list-item-temporal.component'; +import { ItemListItemHeightComponent } from './components/item-list-item-height/item-list-item-height.component'; +import { ItemListItemTipstarComponent } from './components/item-list-item-tipstar/item-list-item-tipstar.component'; +import { ItemListItemWatBalComponent } from './components/item-list-item-watbal/item-list-item-watbal.component'; +import { WidgetStatusComponent } from './components/widget-status/widget-status.component'; +import { ItemListItemTrijntjeComponent } from './components/item-list-item-trijntje/item-list-item-trijntje.component'; +import { ItemListItemShadowComponent } from './components/item-list-item-shadow/item-list-item-shadow.component'; +import { ItemListItemBofekComponent } from './components/item-list-item-bofek/item-list-item-bofek.component'; + + +export function localStorageSyncReducer(reducer: ActionReducer): ActionReducer { + return localStorageSync({ + keys: ['mapState'],rehydrate:true })(reducer); +} + + +@NgModule({ + imports: [ + CommonModule, + AngularOpenlayersModule, + MapRoutingModule, + StoreModule.forFeature(MODULE_NAME, reducer), + EffectsModule.forFeature([mapEffects.MapEffects]), + NgbModule, + FormsModule, + ReactiveFormsModule, + AppCommonModule + ], + declarations: [ + ZoomToExtentComponent, + ItemVectorSourceComponent, + ItemFeaturesSourceComponent, + ItemLayersComponent, + FileDropTargetComponent, + MapComponent, + MetaDataModalComponent, + RotationResetComponent, + MapSearchComponent, + SelectPeriodModalComponent, + LayerListComponent, + LegendComponent, + LayerVectorImageComponent, + FeatureListComponent, + WidgetHostDirective, + FeatureListContainerComponent, + FeatureListComponent, + FeatureListCroppingschemeComponent, + FeatureListCropfieldComponent, + FeatureListFeatureContainerComponent, + FeatureListFeatureComponent, + FeatureListFeatureCroppingschemeComponent, + FeatureListFeatureCropfieldComponent, + SelectedItemContainerComponent, + SelectedItemComponent, + SelectedItemCropfieldComponent, + SelectedItemGeotiffComponent, + SelectedItemShapeComponent, + ItemListItemComponent, + ItemListItemContainerComponent, + ItemListComponent, + ItemWidgetWeatherComponent, + ItemListItemTemporalComponent, + ItemWidgetListComponent, + ItemListItemHeightComponent, + ItemListItemTipstarComponent, + ItemListItemWatBalComponent, + ItemListItemTrijntjeComponent, + ItemListItemShadowComponent, + WidgetStatusComponent, + GpsLocation, + ItemListItemBofekComponent + // Switch2D3DComponent + ], + entryComponents: [ + FeatureListComponent, + FeatureListCroppingschemeComponent, + FeatureListCropfieldComponent, + FeatureListFeatureComponent, + FeatureListFeatureCroppingschemeComponent, + FeatureListFeatureCropfieldComponent, + SelectedItemComponent, + SelectedItemCropfieldComponent, + SelectedItemGeotiffComponent, + SelectedItemShapeComponent, + ItemListComponent, + ItemListItemComponent, + ItemWidgetWeatherComponent, + ItemListItemTemporalComponent, + ItemListItemHeightComponent, + ItemListItemTipstarComponent, + ItemListItemWatBalComponent, + ItemListItemTrijntjeComponent, + ItemListItemShadowComponent, + ItemListItemBofekComponent, + ], + providers: [ + StateSerializerService, + GeolocationService, + { provide: AbstractFeatureListComponent, useClass: FeatureListCroppingschemeComponent, multi: true }, + { provide: AbstractFeatureListComponent, useClass: FeatureListCropfieldComponent, multi: true }, + { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureComponent, multi: true }, + { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureCroppingschemeComponent, multi: true }, + { provide: AbstractFeatureListFeatureComponent, useClass: FeatureListFeatureCropfieldComponent, multi: true }, + { provide: AbstractSelectedItemComponent, useClass: SelectedItemComponent, multi: true }, + { provide: AbstractSelectedItemComponent, useClass: SelectedItemCropfieldComponent, multi: true }, + { provide: AbstractSelectedItemComponent, useClass: SelectedItemGeotiffComponent, multi: true }, + { provide: AbstractSelectedItemComponent, useClass: SelectedItemShapeComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemComponent, multi: true }, + { provide: AbstractItemWidgetComponent, useClass: ItemWidgetWeatherComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemTemporalComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemHeightComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemTipstarComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemWatBalComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemTrijntjeComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemShadowComponent, multi: true }, + { provide: AbstractItemListItemComponent, useClass: ItemListItemBofekComponent, multi: true }, + { provide: AbstractItemListComponent, useClass: ItemListComponent, multi: true } + ], + exports: [ + ZoomToExtentComponent, + ItemVectorSourceComponent, + ItemFeaturesSourceComponent, + ItemLayersComponent, + FileDropTargetComponent, + RotationResetComponent, + LayerListComponent, + LegendComponent, + LayerVectorImageComponent, + WidgetHostDirective, + FeatureListFeatureContainerComponent + ] +}) +export class CommonMapModule { } diff --git a/projects/common-map/src/lib/t/tt/module-name.ts b/projects/common-map/src/lib/t/tt/module-name.ts new file mode 100644 index 0000000..59eec1f --- /dev/null +++ b/projects/common-map/src/lib/t/tt/module-name.ts @@ -0,0 +1 @@ +export const MODULE_NAME = "FarmMapsCommonMap"; diff --git a/projects/common-map/src/public-api.ts b/projects/common-map/src/public-api.ts index cc3004b..2188ce7 100644 --- a/projects/common-map/src/public-api.ts +++ b/projects/common-map/src/public-api.ts @@ -2,6 +2,4 @@ * Public API Surface of common-map */ -export * from './lib/common-map.service'; -export * from './lib/common-map.component'; export * from './lib/common-map.module'; diff --git a/projects/common/scss-bundle.config.json b/projects/common/scss-bundle.config.json new file mode 100644 index 0000000..e69de29 diff --git a/projects/common/src/lib/common.module.ts b/projects/common/src/lib/common.module.ts index 2c56f7e..9aefcab 100644 --- a/projects/common/src/lib/common.module.ts +++ b/projects/common/src/lib/common.module.ts @@ -117,7 +117,16 @@ export {FolderService, TagInputComponent, SessionClearedComponent ], - exports: [NgbModule,UploadxModule, ResumableFileUploadComponent, SidePanelComponent, CommonModule, HttpClientModule, SafePipe, TimespanComponent, TagInputComponent ] + exports: [ResumableFileUploadComponent, + AuthCallbackComponent, + SidePanelComponent, + SafePipe, + NotFoundComponent, + ResumableFileUploadComponent, + TimespanComponent, + TagInputComponent, + SessionClearedComponent + ] }) export class AppCommonModule { static forRoot(): ModuleWithProviders {