Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e3db86ac | ||
|
|
a458a64969 | ||
|
|
f5c00cfa93 | ||
|
|
539643caf4 | ||
|
|
e4e4c3a955 | ||
|
|
55a511e9f1 | ||
|
|
55e0fca470 | ||
|
|
240d0c9997 | ||
|
|
5d75f023ad | ||
|
|
fb978331d9 | ||
|
|
5c54e0568f | ||
|
|
4ae9653e66 | ||
|
|
8e29b9f139 | ||
|
|
f698beb068 | ||
|
|
8c273f5d5e | ||
|
|
9a9a5d1bd6 | ||
|
|
52501275b5 | ||
|
|
eb9477c110 | ||
|
|
d0892f4c57 |
@@ -1,4 +1,4 @@
|
|||||||
# GaneWeb
|
# Life Map Web
|
||||||
|
|
||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.5.
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.5.
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -3,7 +3,7 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"gane-web": {
|
"lm-web": {
|
||||||
"root": "",
|
"root": "",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/gane-web",
|
"outputPath": "dist/lm-web",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
@@ -62,18 +62,18 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "gane-web:build"
|
"browserTarget": "lm-web:build"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "gane-web:build:production"
|
"browserTarget": "lm-web:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "gane-web:build"
|
"browserTarget": "lm-web:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gane-web-e2e": {
|
"lm-web-e2e": {
|
||||||
"root": "e2e/",
|
"root": "e2e/",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"prefix": "",
|
"prefix": "",
|
||||||
@@ -116,11 +116,11 @@
|
|||||||
"builder": "@angular-devkit/build-angular:protractor",
|
"builder": "@angular-devkit/build-angular:protractor",
|
||||||
"options": {
|
"options": {
|
||||||
"protractorConfig": "e2e/protractor.conf.js",
|
"protractorConfig": "e2e/protractor.conf.js",
|
||||||
"devServerTarget": "gane-web:serve"
|
"devServerTarget": "lm-web:serve"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"devServerTarget": "gane-web:serve:production"
|
"devServerTarget": "lm-web:serve:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -136,5 +136,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultProject": "gane-web"
|
"defaultProject": "lm-web"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe('workspace-project App', () => {
|
|||||||
|
|
||||||
it('should display welcome message', () => {
|
it('should display welcome message', () => {
|
||||||
page.navigateTo();
|
page.navigateTo();
|
||||||
expect(page.getTitleText()).toEqual('Welcome to gane-web!');
|
expect(page.getTitleText()).toEqual('Welcome to lm web!');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
Generated
+1702
-1152
File diff suppressed because it is too large
Load Diff
+25
-23
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"layerName": "gane-web",
|
"layerName": "lm-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -11,43 +11,45 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^7.2.12",
|
"@angular/animations": "^7.2.16",
|
||||||
"@angular/cdk": "^7.3.7",
|
"@angular/cdk": "^7.3.7",
|
||||||
"@angular/common": "~7.2.0",
|
"@angular/common": "^7.2.16",
|
||||||
"@angular/compiler": "~7.2.0",
|
"@angular/compiler": "^7.2.16",
|
||||||
"@angular/core": "~7.2.0",
|
"@angular/core": "^7.2.16",
|
||||||
"@angular/forms": "~7.2.0",
|
"@angular/forms": "^7.2.16",
|
||||||
"@angular/material": "^7.3.7",
|
"@angular/material": "^7.3.7",
|
||||||
"@angular/platform-browser": "~7.2.0",
|
"@angular/platform-browser": "^7.2.16",
|
||||||
"@angular/platform-browser-dynamic": "~7.2.0",
|
"@angular/platform-browser-dynamic": "^7.2.16",
|
||||||
"@angular/router": "~7.2.0",
|
"@angular/router": "^7.2.16",
|
||||||
"@types/leaflet": "^1.4.3",
|
"@types/jszip": "^3.1.7",
|
||||||
"@types/openlayers": "^4.6.16",
|
"@types/openlayers": "^4.6.17",
|
||||||
"core-js": "^2.5.4",
|
"core-js": "^2.6.11",
|
||||||
"hammerjs": "^2.0.8",
|
"file-saver": "^2.0.2",
|
||||||
"leaflet": "^1.4.0",
|
"jszip": "^3.2.2",
|
||||||
"ol": "^5.3.1",
|
"ngx-color-picker": "^7.5.0",
|
||||||
|
"ol": "^5.3.3",
|
||||||
"rxjs": "~6.3.3",
|
"rxjs": "~6.3.3",
|
||||||
"tslib": "^1.9.0",
|
"stream": "0.0.2",
|
||||||
|
"tslib": "^1.10.0",
|
||||||
"zone.js": "~0.8.26"
|
"zone.js": "~0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.13.0",
|
"@angular-devkit/build-angular": "^0.13.0",
|
||||||
"@angular/cli": "~7.3.5",
|
"@angular/cli": "^7.3.9",
|
||||||
"@angular/compiler-cli": "~7.2.0",
|
"@angular/compiler-cli": "^7.2.16",
|
||||||
"@angular/language-service": "~7.2.0",
|
"@angular/language-service": "^7.2.16",
|
||||||
"@types/jasmine": "~2.8.8",
|
"@types/jasmine": "~2.8.8",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "^2.0.8",
|
||||||
"@types/node": "~8.9.4",
|
"@types/node": "~8.9.4",
|
||||||
"codelyzer": "~4.5.0",
|
"codelyzer": "~4.5.0",
|
||||||
"jasmine-core": "~2.99.1",
|
"jasmine-core": "~2.99.1",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"karma": "~4.0.0",
|
"karma": "~4.0.0",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
"karma-coverage-istanbul-reporter": "^2.0.6",
|
||||||
"karma-jasmine": "~1.1.2",
|
"karma-jasmine": "~1.1.2",
|
||||||
"karma-jasmine-html-reporter": "^0.2.2",
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
"protractor": "~5.4.0",
|
"protractor": "^5.4.3",
|
||||||
"ts-node": "~7.0.0",
|
"ts-node": "~7.0.0",
|
||||||
"tslint": "~5.11.0",
|
"tslint": "~5.11.0",
|
||||||
"typescript": "~3.2.2"
|
"typescript": "~3.2.2"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {Component, Inject} from '@angular/core';
|
import {Component, Inject} from '@angular/core';
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material";
|
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material';
|
||||||
import {ValidationErrors} from "@angular/forms";
|
import {ValidationErrors} from '@angular/forms';
|
||||||
import {RawLayer} from "../models/RawLayer";
|
import {RawLayer} from '../models/RawLayer';
|
||||||
import {Utils} from "../misc/Utils";
|
import {Utils} from '../misc/Utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Add description
|
* TODO: Add description
|
||||||
@@ -24,10 +24,11 @@ export class AddLayerComponent {
|
|||||||
layerName: '',
|
layerName: '',
|
||||||
layerData: null,
|
layerData: null,
|
||||||
color: '#4fadc2'
|
color: '#4fadc2'
|
||||||
}
|
};
|
||||||
|
|
||||||
public uploadError: ValidationErrors = null;
|
public uploadError: ValidationErrors = null;
|
||||||
public uploadIsDirty: boolean = false;
|
public uploadIsDirty: boolean = false;
|
||||||
public editMode : boolean = false;
|
public editMode: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<AddLayerComponent>,
|
public dialogRef: MatDialogRef<AddLayerComponent>,
|
||||||
@@ -36,7 +37,8 @@ export class AddLayerComponent {
|
|||||||
if (data) {
|
if (data) {
|
||||||
this.formData.layerName = data.name;
|
this.formData.layerName = data.name;
|
||||||
this.formData.layerData = data.data;
|
this.formData.layerData = data.data;
|
||||||
this.formData.color = Utils.rgbToHex(data.color[0],data.color[1],data.color[2]);
|
const colorRgb = data.getDefaultColor();
|
||||||
|
this.formData.color = Utils.rgbToHex(colorRgb[0], colorRgb[1], colorRgb[2]);
|
||||||
this.editMode = true;
|
this.editMode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ export class AddLayerComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.uploadError = {
|
this.uploadError = {
|
||||||
invalidFileType: true
|
invalidFileType: true
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
@@ -75,7 +77,8 @@ export class AddLayerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
const rawLayer = new RawLayer(this.formData.layerName, this.formData.layerData, Utils.hexToRgba(this.formData.color));
|
const rawLayer = new RawLayer(this.formData.layerName, this.formData.layerData);
|
||||||
|
rawLayer.saveDefaultColor(Utils.hexToRgba(this.formData.color));
|
||||||
this.close(rawLayer);
|
this.close(rawLayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import {MapComponent} from "./map/map.component";
|
import {MapComponent} from "./map/map.component";
|
||||||
import {LeafletMapComponent} from "./leaflet-map/leaflet-map.component";
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '**', component: MapComponent},
|
{path: '**', component: MapComponent},
|
||||||
{path: 'osm', component: MapComponent},
|
{path: 'osm', component: MapComponent}
|
||||||
{path: 'leaflet', component: LeafletMapComponent},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ describe('AppComponent', () => {
|
|||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should have as title 'gane-web'`, () => {
|
it(`should have as title 'lmweb'`, () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.debugElement.componentInstance;
|
const app = fixture.debugElement.componentInstance;
|
||||||
expect(app.title).toEqual('gane-web');
|
expect(app.title).toEqual('lm web');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render title in a h1 tag', () => {
|
it('should render title in a h1 tag', () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
const compiled = fixture.debugElement.nativeElement;
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to gane-web!');
|
expect(compiled.querySelector('h1').textContent).toContain('Welcome to lm web!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import 'hammerjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
|||||||
+29
-17
@@ -1,29 +1,37 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
|
import {AppRoutingModule} from './app-routing.module';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import {AppComponent} from './app.component';
|
||||||
import { AppComponent } from './app.component';
|
import {MapComponent} from './map/map.component';
|
||||||
import { MapComponent } from './map/map.component';
|
import {FormsModule} from '@angular/forms';
|
||||||
import { LeafletMapComponent } from './leaflet-map/leaflet-map.component';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {HttpClient, HttpClientModule} from "@angular/common/http";
|
import {
|
||||||
import {FormsModule} from "@angular/forms";
|
MatCardModule,
|
||||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
MatDialogModule,
|
||||||
import {MatCardModule, MatDialogModule, MatGridListModule, MatInputModule, MatToolbarModule} from "@angular/material";
|
MatGridListModule,
|
||||||
import {DragDropModule} from "@angular/cdk/drag-drop";
|
MatInputModule,
|
||||||
import { AddLayerComponent } from './add-layer-component/add-layer.component';
|
MatTableModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
} from '@angular/material';
|
||||||
|
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||||
|
import {AddLayerComponent} from './add-layer-component/add-layer.component';
|
||||||
|
import {FeatureManagerComponent} from './feature-management/feature-manager/feature-manager.component';
|
||||||
|
import {CustomColorEntryComponent} from './feature-management/custom-color-entry/custom-color-entry.component';
|
||||||
|
import {ColorPickerModule} from 'ngx-color-picker';
|
||||||
|
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
MapComponent,
|
MapComponent,
|
||||||
LeafletMapComponent,
|
AddLayerComponent,
|
||||||
AddLayerComponent
|
FeatureManagerComponent,
|
||||||
|
CustomColorEntryComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
MatGridListModule,
|
MatGridListModule,
|
||||||
@@ -31,10 +39,14 @@ import { AddLayerComponent } from './add-layer-component/add-layer.component';
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
|
MatTableModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
ColorPickerModule
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddLayerComponent
|
AddLayerComponent
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* An enum that presents all custom property keys
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
* - add descriptions
|
||||||
|
*/
|
||||||
|
export enum CustomProperties {
|
||||||
|
DEFAULT_COLOR_STROKE = 'default-style-stroke',
|
||||||
|
DEFAULT_COLOR_FILL = 'default-style-fill',
|
||||||
|
COLOR_STROKE = 'style-stroke',
|
||||||
|
COLOR_FILL = 'style-fill',
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="entry">
|
||||||
|
Color:
|
||||||
|
<div class="color-preview" [ngStyle]="previewStyle"></div>
|
||||||
|
<span *ngIf="isDefaultColor">(default color)</span>
|
||||||
|
<div class="controls">
|
||||||
|
<i class="material-icons"
|
||||||
|
[(colorPicker)]="prevColorCache"
|
||||||
|
cpAlphaChannel="disabled"
|
||||||
|
cpOutputFormat="hex"
|
||||||
|
[cpOKButton]="true"
|
||||||
|
[cpCancelButton]="true"
|
||||||
|
(colorPickerSelect)="onColorPicked($event)"
|
||||||
|
>edit</i>
|
||||||
|
<i *ngIf="!isDefaultColor" class="material-icons" (click)="resetColor()">delete</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
height: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 .125rem 0;
|
||||||
|
border: 1px black solid;
|
||||||
|
padding: .5rem;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-preview {
|
||||||
|
margin: 0 .5rem 0 .5rem;
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
border: 2px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {CustomProperties} from '../../enums/CustomProperties';
|
||||||
|
import {Utils} from '../../misc/Utils';
|
||||||
|
import {Config} from '../../misc/Config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component represents an entry of the custom properties section inside the feature management and
|
||||||
|
* is responsible to show and makes changes to the color properties.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'lm-custom-color-entry',
|
||||||
|
templateUrl: 'custom-color-entry.component.html',
|
||||||
|
styleUrls: ['custom-color-entry.component.scss']
|
||||||
|
})
|
||||||
|
export class CustomColorEntryComponent implements OnInit {
|
||||||
|
|
||||||
|
private prevColorStorageKey: string = Config.GEO_JSON_PREFIX + 'prev_selected_color';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color cache to make the previous selected color available
|
||||||
|
*/
|
||||||
|
private _prevColorCache: string;
|
||||||
|
get prevColorCache(): string {
|
||||||
|
return localStorage.getItem(this.prevColorStorageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _colors: any;
|
||||||
|
@Input() set colors(value: any) {
|
||||||
|
this._colors = value;
|
||||||
|
if (this._colors[CustomProperties.COLOR_STROKE]) {
|
||||||
|
this.isDefaultColor = false;
|
||||||
|
this.definePreviewStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get colors(): any {
|
||||||
|
return this._colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selectedColor: string;
|
||||||
|
set selectedColor(value: string) {
|
||||||
|
this._selectedColor = value;
|
||||||
|
this.applyColor(value);
|
||||||
|
this.colorChangeEmitter.emit(this.colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedColor(): string {
|
||||||
|
return this._selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO diese funktionen in definePreviewStyles direkt aufrufen
|
||||||
|
// - default color properties
|
||||||
|
get defaultStrokeColorAsRgba(): string {
|
||||||
|
const color = this.colors[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||||
|
return Utils.colorArrayToRgba(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultFillColorAsRgba(): string {
|
||||||
|
const color = this.colors[CustomProperties.DEFAULT_COLOR_FILL];
|
||||||
|
return Utils.colorArrayToRgba(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - no default color properties
|
||||||
|
get strokeColorAsRgba(): string {
|
||||||
|
const color = this.colors[CustomProperties.COLOR_STROKE];
|
||||||
|
return Utils.colorArrayToRgba(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fillColorAsRgba(): string {
|
||||||
|
const color = this.colors[CustomProperties.COLOR_FILL];
|
||||||
|
return Utils.colorArrayToRgba(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Output('colorChanged') colorChangeEmitter: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color style of the color preview
|
||||||
|
*/
|
||||||
|
previewStyle: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple flag that is true, if the default color is active
|
||||||
|
*/
|
||||||
|
isDefaultColor: boolean = true;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.definePreviewStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the style for the preview. If a color was defined, this color is used. If not, the default color is used
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
definePreviewStyle() {
|
||||||
|
let styles = {
|
||||||
|
'border-color': this.defaultStrokeColorAsRgba,
|
||||||
|
'background-color': this.defaultFillColorAsRgba
|
||||||
|
};
|
||||||
|
if (!this.isDefaultColor) {
|
||||||
|
styles = {
|
||||||
|
'border-color': this.strokeColorAsRgba,
|
||||||
|
'background-color': this.fillColorAsRgba
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.previewStyle = styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given hex value to colors object as not default color
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
applyColor(value: string) {
|
||||||
|
this.colors[CustomProperties.COLOR_STROKE] = Utils.hexToRgba(value);
|
||||||
|
this.colors[CustomProperties.COLOR_FILL] = Utils.hexToRgba(value, Config.DEFAULT_FILL_OPACITY_TO_STROKE);
|
||||||
|
this.definePreviewStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the custom color
|
||||||
|
*/
|
||||||
|
resetColor() {
|
||||||
|
this.colors[CustomProperties.COLOR_STROKE] = null;
|
||||||
|
this.colors[CustomProperties.COLOR_FILL] = null;
|
||||||
|
this.isDefaultColor = true;
|
||||||
|
this._selectedColor = null;
|
||||||
|
this.definePreviewStyle();
|
||||||
|
this.colorChangeEmitter.emit(this.colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eventhandler that is fired when a color is picked
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onColorPicked(color: string) {
|
||||||
|
localStorage.setItem(this.prevColorStorageKey, color);
|
||||||
|
this.selectedColor = color;
|
||||||
|
this.isDefaultColor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div *ngIf="curFeature" class="property-list">
|
||||||
|
<div class="header">
|
||||||
|
<h4>Custom Properties</h4>
|
||||||
|
</div>
|
||||||
|
<lm-custom-color-entry [colors]="curFeatureColors"
|
||||||
|
(colorChanged)="onFeatureColorChanged($event)"></lm-custom-color-entry>
|
||||||
|
<div class="header">
|
||||||
|
<h4>Properties</h4>
|
||||||
|
</div>
|
||||||
|
<div *ngFor="let item of curProperties | keyvalue" class="entry">
|
||||||
|
<span class="property-name">{{item.key}}</span> : <span class="property-value">{{item.value}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.header,
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
height: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 .125rem 0;
|
||||||
|
border: 1px black solid;
|
||||||
|
padding: .5rem;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-name {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-value {
|
||||||
|
margin: 0 0 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {Component, Input, Output, EventEmitter} from '@angular/core';
|
||||||
|
import {Config} from '../../misc/Config';
|
||||||
|
import {CustomProperties} from '../../enums/CustomProperties';
|
||||||
|
import {Feature} from 'ol/Feature';
|
||||||
|
import {RawLayer} from 'src/app/models/RawLayer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component manages all properties of the current feature. It does not matter if it is a simple geo json
|
||||||
|
* property or a custom property (custom properties are special properties that are just understand by life map
|
||||||
|
* like the color of the feature). All changes that were made to the properties are saved in the geo json of
|
||||||
|
* the RawLayer object from which the open layer feature based.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - If a layer with many properties is selected, the list of these properties is too long. A better design has to be used.
|
||||||
|
* -
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'lm-feature-manager',
|
||||||
|
templateUrl: 'feature-manager.component.html',
|
||||||
|
styleUrls: ['feature-manager.component.scss']
|
||||||
|
})
|
||||||
|
export class FeatureManagerComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The feature that is selected to manage
|
||||||
|
*/
|
||||||
|
private _curFeature: Feature;
|
||||||
|
@Input() set curFeature(value: Feature) {
|
||||||
|
this._curFeature = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get curFeature(): Feature {
|
||||||
|
return this._curFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event emitter that is fired if a feature is changed,
|
||||||
|
*/
|
||||||
|
@Output('featureChange') featureChangeEmitter: EventEmitter<Feature> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the properties of curFeature
|
||||||
|
*/
|
||||||
|
get curProperties(): any {
|
||||||
|
const properties = this.curFeature.getProperties();
|
||||||
|
delete properties.geometry;
|
||||||
|
delete properties[Config.GEO_JSON_PREFIX];
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the custom properties of curFeature
|
||||||
|
*/
|
||||||
|
get curCustomProperties(): any {
|
||||||
|
const properties = this.curFeature.getProperties();
|
||||||
|
return properties[Config.GEO_JSON_PREFIX];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default color properties of the current feature
|
||||||
|
*/
|
||||||
|
get curFeatureColors(): any {
|
||||||
|
const colors = {};
|
||||||
|
colors[CustomProperties.DEFAULT_COLOR_STROKE] = this.curCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||||
|
colors[CustomProperties.DEFAULT_COLOR_FILL] = this.curCustomProperties[CustomProperties.DEFAULT_COLOR_FILL];
|
||||||
|
colors[CustomProperties.COLOR_STROKE] = this.curCustomProperties[CustomProperties.COLOR_STROKE];
|
||||||
|
colors[CustomProperties.COLOR_FILL] = this.curCustomProperties[CustomProperties.COLOR_FILL];
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler if the color set of a feature was changed
|
||||||
|
* @param colors
|
||||||
|
*/
|
||||||
|
onFeatureColorChanged(colors: any) {
|
||||||
|
RawLayer.saveCustomProperties(this.curFeature, colors);
|
||||||
|
this.curFeature.setStyle(RawLayer.determineColorStyle(colors));
|
||||||
|
this.featureChangeEmitter.emit(this.curFeature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<div id="mapid"></div>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#mapid { height: 50vh; }
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { LeafletMapComponent } from './leaflet-map.component';
|
|
||||||
|
|
||||||
describe('LeafletMapComponent', () => {
|
|
||||||
let component: LeafletMapComponent;
|
|
||||||
let fixture: ComponentFixture<LeafletMapComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ LeafletMapComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(LeafletMapComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
|
||||||
import * as L from 'Leaflet';
|
|
||||||
import {HttpClient} from "@angular/common/http";
|
|
||||||
import {GeoJSON} from "Leaflet";
|
|
||||||
import {GeoJsonObject} from "geojson";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-leaflet-map',
|
|
||||||
templateUrl: './leaflet-map.component.html',
|
|
||||||
styleUrls: ['./leaflet-map.component.scss']
|
|
||||||
})
|
|
||||||
export class LeafletMapComponent implements OnInit {
|
|
||||||
|
|
||||||
private map;
|
|
||||||
|
|
||||||
private vectorLayerFiles: string[] = [
|
|
||||||
'./assets/geolayer/flurstuecke-nutzer-baum-kompensation.geojson',
|
|
||||||
// './assets/geolayer/bebauung.geojson',
|
|
||||||
'./assets/geolayer/feldwege.geojson',
|
|
||||||
'./assets/geolayer/gehoelze.geojson',
|
|
||||||
'./assets/geolayer/gewaesser.geojson',
|
|
||||||
// './assets/geolayer/grabensysteme.geojson',
|
|
||||||
// './assets/geolayer/grabenuebergaenge.geojson',
|
|
||||||
// './assets/geolayer/gsp_einsatzgebiete.geojson',
|
|
||||||
// './assets/geolayer/stromleitungen.geojson',
|
|
||||||
// './assets/geolayer/strommasten.geojson',
|
|
||||||
// './assets/geolayer/windenergieanlagen.geojson',
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
|
|
||||||
this.map = L.map('mapid').setView([53.1582, 8.7728], 13);
|
|
||||||
// L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
// attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
||||||
// }).addTo(this.map);
|
|
||||||
|
|
||||||
// L.marker([53.1582, 8.7728]).addTo(this.map)
|
|
||||||
// .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
|
|
||||||
// .openPopup();
|
|
||||||
|
|
||||||
const layers : any[] = await this.getGeoJsons();
|
|
||||||
layers.forEach(layer => L.geoJSON(layer).addTo(this.map));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGeoJsons() {
|
|
||||||
const promises = this.vectorLayerFiles.map(file => this.http.get(file).toPromise());
|
|
||||||
return await Promise.all(promises);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,15 +16,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- layer property area-->
|
<!-- layer property area-->
|
||||||
<div class="layer-property-area">
|
<div class="property-list-area">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>Layer Properties</mat-card-title>
|
<mat-card-title>Layer Properties</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div>
|
<lm-feature-manager [curFeature]="cache.selectedFeature"></lm-feature-manager>
|
||||||
{{ currentFeatureInfo | json}}
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,32 +30,39 @@
|
|||||||
<div class="layer-control-area">
|
<div class="layer-control-area">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>Layer</mat-card-title>
|
<mat-card-title>Layer
|
||||||
|
</mat-card-title>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<i class="material-icons" (click)="addLayer()">add_circle</i>
|
<i class="material-icons"
|
||||||
|
matTooltip="Über diese Schaltfläche können Sie neue Layer hinzufügen.
|
||||||
|
Bitte beachten Sie, dass die Reihenfolge der Layer in der Liste umgekehrt gegenüber Reihenfolge in der Map ist."
|
||||||
|
(click)="addLayer()">add_circle</i>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div cdkDropList class="layer-list" (cdkDropListDropped)="drop($event)">
|
<div cdkDropList class="layer-list" (cdkDropListDropped)="drop($event)">
|
||||||
<div class="list-entry" *ngFor="let layer of customLayerCollection.getArray();let i = index" cdkDrag>
|
<div class="list-entry" *ngFor="let layer of customLayerCollection.getArray();let i = index" cdkDrag>
|
||||||
<h4>{{layer.get('name') }}</h4>
|
<h4 class="title">{{layer.get('name') }}</h4>
|
||||||
<i class="material-icons" (click)="centerLayerOnMap(layer)">
|
<div class="layer-controls">
|
||||||
center_focus_strong
|
<i class="material-icons" (click)="centerLayerOnMap(layer)">
|
||||||
</i>
|
center_focus_strong
|
||||||
<i class="material-icons" (click)="toggleLayerVisibility(layer)">
|
</i>
|
||||||
{{layer.getVisible() ? 'visibility' : 'visibility_off'}}
|
<i class="material-icons" (click)="toggleLayerVisibility(layer)">
|
||||||
</i>
|
{{layer.getVisible() ? 'visibility' : 'visibility_off'}}
|
||||||
<i class="material-icons" (click)="editLayer(i)">
|
</i>
|
||||||
edit
|
<i class="material-icons" (click)="editLayer(i)">
|
||||||
</i>
|
edit
|
||||||
<i class="material-icons" (click)="deleteLayer(layer)">
|
</i>
|
||||||
delete
|
<i class="material-icons" (click)="deleteLayer(layer)">
|
||||||
</i>
|
delete
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button mat-button (click)="saveLayers()">Speichern</button>
|
<button mat-button (click)="exportLayerForApp()">Export für App</button>
|
||||||
|
<button mat-button (click)="exportLayerAsGeoJson()">Export als GeoJSON</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,7 +38,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layer-control-area {
|
.layer-control-area {
|
||||||
|
max-width:25vw;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.layer-list {
|
.layer-list {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -51,18 +55,31 @@
|
|||||||
|
|
||||||
|
|
||||||
.list-entry {
|
.list-entry {
|
||||||
padding: 20px 10px;
|
padding: 10px;
|
||||||
border-bottom: solid 1px #ccc;
|
border-bottom: solid 1px #ccc;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
background: white;
|
background: white;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 10px 0px 10px 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-controls {
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -73,6 +90,10 @@
|
|||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.property-list-area {
|
||||||
|
max-width: 25vw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cdk-drag-preview {
|
.cdk-drag-preview {
|
||||||
|
|||||||
+304
-69
@@ -5,17 +5,33 @@ import TileLayer from 'ol/layer/Tile';
|
|||||||
import {fromLonLat, transform} from 'ol/proj';
|
import {fromLonLat, transform} from 'ol/proj';
|
||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
import {OSM} from 'ol/source';
|
import {OSM} from 'ol/source';
|
||||||
import {CdkDragDrop} from "@angular/cdk/drag-drop";
|
import {CdkDragDrop} from '@angular/cdk/drag-drop';
|
||||||
import {MatDialog} from "@angular/material";
|
import {MatDialog} from '@angular/material';
|
||||||
import {AddLayerComponent} from "../add-layer-component/add-layer.component";
|
import {AddLayerComponent} from '../add-layer-component/add-layer.component';
|
||||||
import {RawLayer} from "../models/RawLayer";
|
import {RawLayer} from '../models/RawLayer';
|
||||||
import LayerGroup from 'ol/layer/Group';
|
import LayerGroup from 'ol/layer/Group';
|
||||||
import {Collection} from "openlayers";
|
import {Collection} from 'openlayers';
|
||||||
import {EPSG} from "../enums/EPSG";
|
import {EPSG} from '../enums/EPSG';
|
||||||
import {inAndOut} from 'ol/easing';
|
import {inAndOut} from 'ol/easing';
|
||||||
|
import {Feature} from 'ol/Feature';
|
||||||
|
import Style from 'ol/style/Style';
|
||||||
|
import Stroke from 'ol/style/Stroke';
|
||||||
|
import Fill from 'ol/style/Fill';
|
||||||
|
import {FeatureManagerComponent} from '../feature-management/feature-manager/feature-manager.component';
|
||||||
|
import {saveAs} from 'file-saver';
|
||||||
|
import {Config} from '../misc/Config';
|
||||||
|
import * as JSZip from 'jszip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: add description
|
* TODO: add description
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
* - Sometimes the cursor is not there where it is displayed
|
||||||
|
* - The order of the layers inside the layer menu does not reflect the real order on the map
|
||||||
|
* - extend feature manager to handle more properties
|
||||||
|
* - The design und ux of the whole application has to be improved
|
||||||
|
* - Add bunch edit to ease coloring of the features
|
||||||
|
* - add possibility to filter for features to color them easier (or add some kind of custom query language)
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-map',
|
selector: 'app-map',
|
||||||
@@ -25,6 +41,7 @@ import {inAndOut} from 'ol/easing';
|
|||||||
export class MapComponent implements OnInit {
|
export class MapComponent implements OnInit {
|
||||||
|
|
||||||
@ViewChild('mapContainer') mapContainer: ElementRef<HTMLDivElement>;
|
@ViewChild('mapContainer') mapContainer: ElementRef<HTMLDivElement>;
|
||||||
|
@ViewChild(FeatureManagerComponent) featureManager: FeatureManagerComponent;
|
||||||
public map: Map;
|
public map: Map;
|
||||||
public baseLayer: TileLayer;
|
public baseLayer: TileLayer;
|
||||||
|
|
||||||
@@ -46,17 +63,50 @@ export class MapComponent implements OnInit {
|
|||||||
public cursorCoordinates: string;
|
public cursorCoordinates: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The properties of the current feature. The current layer is the layer beneath the cursor.
|
* A simple cache that holds different Features and styles
|
||||||
*/
|
*/
|
||||||
public currentFeatureInfo: any;
|
private cache: { [key: string]: Feature | Style } = {
|
||||||
|
/**
|
||||||
|
* Contains the feature that is currently selected
|
||||||
|
*/
|
||||||
|
selectedFeature: null,
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * The layer of the selected feature
|
||||||
|
// */
|
||||||
|
// selectedLayer: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The feature that was highlighted at least
|
||||||
|
*/
|
||||||
|
lastHighlightedFeature: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The style of the last highlighted feature before it was highlighted
|
||||||
|
*/
|
||||||
|
lastHighlightedStyle: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default style for selected feature
|
||||||
|
*/
|
||||||
|
defaultSelectedStyle: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default style for a highlighted feature
|
||||||
|
*/
|
||||||
|
defaultHighlightStyle: null
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
constructor(public dialog: MatDialog) {
|
constructor(public dialog: MatDialog) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.initCache();
|
||||||
this.initBaseLayer();
|
this.initBaseLayer();
|
||||||
this.initLayerGroup();
|
this.initLayerGroup();
|
||||||
this.initMap();
|
this.initMap();
|
||||||
|
this.addMockData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,18 +143,18 @@ export class MapComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addMapEventlistener();
|
this.addMapEventListener();
|
||||||
|
this.addFeatureManagerEventListener();
|
||||||
// without this line the map is only visible after resizing window
|
// without this line the map is only visible after resizing window
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.map.updateSize();
|
this.map.updateSize();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds all map event listener
|
* Adds all map event listener
|
||||||
*/
|
*/
|
||||||
private addMapEventlistener() {
|
private addMapEventListener() {
|
||||||
// get current cursor coordinates to transform and display them
|
// get current cursor coordinates to transform and display them
|
||||||
this.map.on('pointermove', (evt) => {
|
this.map.on('pointermove', (evt) => {
|
||||||
const tmpCoords = transform(evt.coordinate, EPSG.openLayersDefault, EPSG.wgs84);
|
const tmpCoords = transform(evt.coordinate, EPSG.openLayersDefault, EPSG.wgs84);
|
||||||
@@ -113,71 +163,92 @@ export class MapComponent implements OnInit {
|
|||||||
|
|
||||||
// add event to get feature infos
|
// add event to get feature infos
|
||||||
this.map.on('pointermove', (evt) => {
|
this.map.on('pointermove', (evt) => {
|
||||||
if (evt.dragging) {
|
if (evt.dragging || this.cache.selectedFeature) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pixel = this.map.getEventPixel(evt.originalEvent);
|
const pixel = this.map.getEventPixel(evt.originalEvent);
|
||||||
this.displayFeatureInfo(pixel);
|
const features = this.map.getFeaturesAtPixel(pixel);
|
||||||
|
|
||||||
|
if (features && features.length > 0) {
|
||||||
|
this.map.getTargetElement().style.cursor = 'pointer';
|
||||||
|
this.featureManager.curFeature = features[0];
|
||||||
|
this.highlightFeature(features[0]);
|
||||||
|
} else {
|
||||||
|
this.map.getTargetElement().style.cursor = '';
|
||||||
|
// reapply original style to last highlighted feature
|
||||||
|
if (this.cache.lastHighlightedFeature) {
|
||||||
|
this.cache.lastHighlightedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||||
|
}
|
||||||
|
// reset cache for feature and style
|
||||||
|
this.cache.lastHighlightedFeature = null;
|
||||||
|
this.cache.lastHighlightedStyle = null;
|
||||||
|
this.featureManager.curFeature = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle feature selection
|
||||||
|
this.map.on('click', (evt) => {
|
||||||
|
const pixel = this.map.getEventPixel(evt.originalEvent);
|
||||||
|
// const features = this.map.getFeaturesAtPixel(pixel);
|
||||||
|
let selectedFeature;
|
||||||
|
// let selectedLayer;
|
||||||
|
// get the feature and the according layer
|
||||||
|
this.map.forEachFeatureAtPixel(pixel, (feature, layer) => {
|
||||||
|
selectedFeature = feature;
|
||||||
|
// selectedLayer = layer;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.cache.selectedFeature) {
|
||||||
|
// use lastHighlightedStyle because the original style is saved there
|
||||||
|
if (this.cache.lastHighlightedStyle) {
|
||||||
|
this.cache.selectedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||||
|
}
|
||||||
|
this.cache.selectedFeature = null;
|
||||||
|
// this.cache.selectedLayer = null;
|
||||||
|
} else if (selectedFeature) {
|
||||||
|
this.map.getTargetElement().style.cursor = '';
|
||||||
|
this.cache.selectedFeature = selectedFeature;
|
||||||
|
// this.cache.selectedLayer = selectedLayer;
|
||||||
|
this.featureManager.curFeature = this.cache.selectedFeature;
|
||||||
|
this.cache.lastHighlightedFeature = null;
|
||||||
|
this.cache.selectedFeature.setStyle(this.cache.defaultSelectedStyle);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all feature infos for each feature at the given pixel.
|
* Adds all feature manager event listener
|
||||||
* @param pixel
|
|
||||||
*/
|
*/
|
||||||
private displayFeatureInfo(pixel) {
|
private addFeatureManagerEventListener() {
|
||||||
const features = [];
|
this.featureManager.featureChangeEmitter.subscribe(() => {
|
||||||
this.map.forEachFeatureAtPixel(pixel, (feature) => {
|
// - reset selection values
|
||||||
features.push(feature)
|
// this.cache.selectedFeature = null;
|
||||||
}
|
this.cache.lastHighlightedStyle = null;
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (features.length == 0) {
|
/**
|
||||||
this.currentFeatureInfo = 'Too many features. Please zoom in to see feature infos';
|
* Highlights a Features
|
||||||
} else if (features.length == 1) {
|
* @param feature
|
||||||
features.forEach(feature => {
|
*/
|
||||||
const properties = feature.getProperties();
|
private highlightFeature(feature: Feature) {
|
||||||
delete properties.geometry;
|
// reapply saved feature and style
|
||||||
this.currentFeatureInfo = properties;
|
if (this.cache.lastHighlightedFeature) {
|
||||||
})
|
this.cache.lastHighlightedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||||
} else {
|
|
||||||
this.currentFeatureInfo = null;
|
|
||||||
}
|
}
|
||||||
};
|
// save current feature and style
|
||||||
|
this.cache.lastHighlightedFeature = feature;
|
||||||
|
this.cache.lastHighlightedStyle = feature.getStyle();
|
||||||
|
|
||||||
// private colorPolygons(feature: Feature, resolution: any) {
|
// set new feature style
|
||||||
// const styles = {
|
feature.setStyle(this.cache.defaultHighlightStyle);
|
||||||
// black: new Style({
|
}
|
||||||
// stroke: new Stroke({
|
|
||||||
// color: '#86665a',
|
|
||||||
// width: 1
|
|
||||||
// }),
|
|
||||||
// fill: new Fill({
|
|
||||||
// color: 'rgba(121,85,72, 0.4)',
|
|
||||||
// })
|
|
||||||
// }),
|
|
||||||
// green: new Style({
|
|
||||||
// stroke: new Stroke({
|
|
||||||
// color: '#b8c633',
|
|
||||||
// width: 1
|
|
||||||
// }),
|
|
||||||
// fill: new Fill({
|
|
||||||
// color: 'rgba(205,220,57,0.4)',
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// // console.log(feature.getProperties()['Grünland'],feature.getProperties());
|
|
||||||
// if (feature.getProperties()['Nutzung'] === 'Grünland') {
|
|
||||||
// return styles.green;
|
|
||||||
// } else {
|
|
||||||
// return styles.black;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a dialog to create a new custom layer
|
* Opens a dialog to create a new custom layer
|
||||||
*/
|
*/
|
||||||
addLayer(): void {
|
public addLayer(): void {
|
||||||
const dialogRef = this.dialog.open(AddLayerComponent);
|
const dialogRef = this.dialog.open(AddLayerComponent);
|
||||||
dialogRef.afterClosed().subscribe(rawLayer => {
|
dialogRef.afterClosed().subscribe(rawLayer => {
|
||||||
if (rawLayer) {
|
if (rawLayer) {
|
||||||
@@ -192,7 +263,7 @@ export class MapComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Opens a dialog to edit a custom layer
|
* Opens a dialog to edit a custom layer
|
||||||
*/
|
*/
|
||||||
editLayer(index: number): void {
|
public editLayer(index: number): void {
|
||||||
const vectorLayer = this.customLayerCollection.item(index);
|
const vectorLayer = this.customLayerCollection.item(index);
|
||||||
const rawLayer = RawLayer.fromVectorLayer(vectorLayer);
|
const rawLayer = RawLayer.fromVectorLayer(vectorLayer);
|
||||||
const dialogRef = this.dialog.open(AddLayerComponent, {data: rawLayer});
|
const dialogRef = this.dialog.open(AddLayerComponent, {data: rawLayer});
|
||||||
@@ -213,13 +284,17 @@ export class MapComponent implements OnInit {
|
|||||||
* Updates the customLayer collection according to drag and drop actions.
|
* Updates the customLayer collection according to drag and drop actions.
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
drop(event: CdkDragDrop<RawLayer[]>) {
|
public drop(event: CdkDragDrop<RawLayer[]>) {
|
||||||
const tmp = this.customLayerCollection.item(event.previousIndex);
|
const tmp = this.customLayerCollection.item(event.previousIndex);
|
||||||
this.customLayerCollection.removeAt(event.previousIndex);
|
this.customLayerCollection.removeAt(event.previousIndex);
|
||||||
this.customLayerCollection.insertAt(event.currentIndex, tmp);
|
this.customLayerCollection.insertAt(event.currentIndex, tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLayer(layer: VectorLayer) {
|
/**
|
||||||
|
* Deletes a layer from custom layer collection
|
||||||
|
* @param layer
|
||||||
|
*/
|
||||||
|
public deleteLayer(layer: VectorLayer) {
|
||||||
this.customLayerCollection.remove(layer);
|
this.customLayerCollection.remove(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,14 +302,51 @@ export class MapComponent implements OnInit {
|
|||||||
* Toggles the visibility of a custom layer
|
* Toggles the visibility of a custom layer
|
||||||
* @param index
|
* @param index
|
||||||
*/
|
*/
|
||||||
toggleLayerVisibility(layer: VectorLayer) {
|
public toggleLayerVisibility(layer: VectorLayer) {
|
||||||
layer.setVisible(!layer.getVisible());
|
layer.setVisible(!layer.getVisible());
|
||||||
}
|
}
|
||||||
|
|
||||||
saveLayers(): RawLayer[] {
|
/**
|
||||||
|
* Export all Layers in one file for app usage
|
||||||
|
*/
|
||||||
|
public exportLayerForApp(): void {
|
||||||
const rawLayers = [];
|
const rawLayers = [];
|
||||||
this.customLayerCollection.forEach(layer => rawLayers.push(RawLayer.fromVectorLayer(layer)));
|
this.customLayerCollection.forEach(layer => rawLayers.push(RawLayer.fromVectorLayer(layer)));
|
||||||
return rawLayers;
|
|
||||||
|
// process raw layer to avoid escaping in data property e. g. {\"type\": \"aaa\"...
|
||||||
|
// the geo json string in data property comes like this from openlayer function GeoJSON().writeFeatures
|
||||||
|
const processedRawLayers = rawLayers.map(rawLayer => {
|
||||||
|
return {
|
||||||
|
name: rawLayer.name,
|
||||||
|
data: JSON.parse(rawLayer.data)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const blob = new Blob([JSON.stringify(processedRawLayers)], {type: 'application/json;charset=utf-8'});
|
||||||
|
saveAs(blob, `app_export_${new Date().toISOString()}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports all layer as single geoJson files in a zip for later processing
|
||||||
|
*/
|
||||||
|
public exportLayerAsGeoJson(): void {
|
||||||
|
const zipArchive = new JSZip();
|
||||||
|
const rawLayers = [];
|
||||||
|
this.customLayerCollection.forEach(layer => rawLayers.push(RawLayer.fromVectorLayer(layer)));
|
||||||
|
|
||||||
|
// create a zip archive
|
||||||
|
rawLayers.forEach(rawLayer => {
|
||||||
|
// process raw layer with JSON.stringify(JSON.parse(...)) to avoid escaping in data property e. g. {\"type\": \"aaa\"...
|
||||||
|
// the geo json string in data property comes like this from openlayer function GeoJSON().writeFeatures
|
||||||
|
const fileContent = new Blob([JSON.stringify(JSON.parse(rawLayer.data))], {type: 'application/geo+json;charset=utf-8'});
|
||||||
|
const fileName = `${rawLayer.name}.geojson`;
|
||||||
|
zipArchive.file(fileName, fileContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save zip archive
|
||||||
|
zipArchive.generateAsync({type: 'blob'})
|
||||||
|
.then((blob) => {
|
||||||
|
saveAs(blob, `lm_export_${new Date().toISOString()}.zip`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +358,130 @@ export class MapComponent implements OnInit {
|
|||||||
size: this.map.getSize(),
|
size: this.map.getSize(),
|
||||||
duration: 888,
|
duration: 888,
|
||||||
easing: inAndOut
|
easing: inAndOut
|
||||||
}
|
};
|
||||||
this.map.getView().fit(customLayer.getSource().getExtent(), options);
|
this.map.getView().fit(customLayer.getSource().getExtent(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support function that inits cache values
|
||||||
|
*/
|
||||||
|
private initCache() {
|
||||||
|
this.cache.defaultSelectedStyle = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgb(245, 64, 41)',
|
||||||
|
width: 3
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(245, 64, 41, 0.3)',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cache.defaultHighlightStyle = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgb(245, 64, 41)',
|
||||||
|
width: 2
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(245, 64, 41, 0.1)',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeBUG DEBUG DBEUG
|
||||||
|
*/
|
||||||
|
private addMockData() {
|
||||||
|
const data = {
|
||||||
|
'type': 'FeatureCollection',
|
||||||
|
'name': 'background',
|
||||||
|
'crs': {
|
||||||
|
'type': 'name',
|
||||||
|
'properties': {
|
||||||
|
'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'features': [{
|
||||||
|
'type': 'Feature',
|
||||||
|
'properties': {
|
||||||
|
'name': 'Feature 1'
|
||||||
|
},
|
||||||
|
'geometry': {
|
||||||
|
'type': 'MultiPolygon',
|
||||||
|
'coordinates': [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[8.801864395832126, 53.14770721254275],
|
||||||
|
[8.806370506977146, 53.14770721254275],
|
||||||
|
[8.806542168354099, 53.14500456859233],
|
||||||
|
[8.80203605720908, 53.14521049030746]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'Feature',
|
||||||
|
'properties': {
|
||||||
|
'name': 'Feature 2'
|
||||||
|
},
|
||||||
|
'geometry': {
|
||||||
|
'type': 'MultiPolygon',
|
||||||
|
'coordinates': [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[8.807787400997881, 53.1477644198588],
|
||||||
|
[8.808044893063311, 53.145010298994144],
|
||||||
|
[8.813452226437335, 53.145010298994144],
|
||||||
|
[8.813194734371905, 53.1477644198588]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'Feature',
|
||||||
|
'properties': {
|
||||||
|
'name': 'Feature 3'
|
||||||
|
},
|
||||||
|
'geometry': {
|
||||||
|
'type': 'MultiPolygon',
|
||||||
|
'coordinates': [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[8.80207966021419, 53.14392919393571],
|
||||||
|
[8.802122575558428, 53.138780701270434],
|
||||||
|
[8.80632827929378, 53.13896090893504],
|
||||||
|
[8.806542856014971, 53.14410938000193]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'Feature',
|
||||||
|
'properties': {
|
||||||
|
'name': 'Feature 4'
|
||||||
|
},
|
||||||
|
'geometry': {
|
||||||
|
'type': 'MultiPolygon',
|
||||||
|
'coordinates': [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[8.808654874418039, 53.143631496039035],
|
||||||
|
[8.809770673368234, 53.1391523112723],
|
||||||
|
[8.816036313627023, 53.13930677369489],
|
||||||
|
[8.8156071601846, 53.14352853151957]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const rawLayer = new RawLayer('Mock Data Layer', JSON.stringify(data));
|
||||||
|
rawLayer.saveDefaultColor([125, 125, 125]);
|
||||||
|
const customLayer = RawLayer.toVectorLayer(rawLayer);
|
||||||
|
this.centerLayerOnMap(customLayer);
|
||||||
|
this.customLayerCollection.push(customLayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export class Config{
|
export class Config{
|
||||||
static readonly GEO_JSON_PREFIX: string = 'GANE';
|
static readonly GEO_JSON_PREFIX: string = 'LM';
|
||||||
|
static readonly DEFAULT_FILL_OPACITY_TO_STROKE: number = 0.1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,20 @@ export class Utils {
|
|||||||
opacity
|
opacity
|
||||||
] : null;
|
] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static colorArrayToRgba(colors: number[]): string{
|
||||||
|
let tmp = 'rgba(';
|
||||||
|
for(let i = 0; i < 4; i++){
|
||||||
|
const color = colors[i];
|
||||||
|
if(color){
|
||||||
|
tmp+= color+',';
|
||||||
|
}else {
|
||||||
|
tmp += '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove last comma
|
||||||
|
tmp = tmp.replace(/,\s*$/, "");
|
||||||
|
tmp += ')';
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-52
@@ -1,11 +1,13 @@
|
|||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
import GeoJSON from 'ol/format/GeoJSON';
|
import GeoJSON from 'ol/format/GeoJSON';
|
||||||
import {Vector as VectorSource} from 'ol/source';
|
import {Vector as VectorSource} from 'ol/source';
|
||||||
import {EPSG} from "../enums/EPSG";
|
import {EPSG} from '../enums/EPSG';
|
||||||
import Style from 'ol/style/Style';
|
import Style from 'ol/style/Style';
|
||||||
import Stroke from 'ol/style/Stroke';
|
import Stroke from 'ol/style/Stroke';
|
||||||
import Fill from 'ol/style/Fill';
|
import Fill from 'ol/style/Fill';
|
||||||
import {Config} from "../misc/Config";
|
import {Config} from '../misc/Config';
|
||||||
|
import {Feature} from 'ol/Feature';
|
||||||
|
import {CustomProperties} from '../enums/CustomProperties';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a raw (not processed) map layer
|
* Represents a raw (not processed) map layer
|
||||||
@@ -19,15 +21,8 @@ export class RawLayer {
|
|||||||
/**
|
/**
|
||||||
* Data of Layer (normally in geojson format)
|
* Data of Layer (normally in geojson format)
|
||||||
*/
|
*/
|
||||||
public data: string,
|
public data: string
|
||||||
/**
|
) {}
|
||||||
* Layer stroke color as number array with opacity like -> [255, 255, 255, 0.5]
|
|
||||||
* The fill color is an opaque decreased derivation of this value.
|
|
||||||
*/
|
|
||||||
public color: number[]
|
|
||||||
) {
|
|
||||||
this.saveColorToGeoJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a RawLayer instance from a VectorLayer and saves the style properties in geojson. Consider that the
|
* Creates a RawLayer instance from a VectorLayer and saves the style properties in geojson. Consider that the
|
||||||
@@ -35,46 +30,24 @@ export class RawLayer {
|
|||||||
* @param layer
|
* @param layer
|
||||||
*/
|
*/
|
||||||
public static fromVectorLayer(layer: VectorLayer): RawLayer {
|
public static fromVectorLayer(layer: VectorLayer): RawLayer {
|
||||||
const writer = new GeoJSON();
|
|
||||||
const source = layer.getSource();
|
const source = layer.getSource();
|
||||||
|
|
||||||
// get default style values
|
|
||||||
const features = source.getFeatures();
|
const features = source.getFeatures();
|
||||||
const featureProperties = features[0].getProperties();
|
const geoJSONString = RawLayer.featuresToGeoJSON(features);
|
||||||
const rawLayerColor = featureProperties[Config.GEO_JSON_PREFIX]['default-style-stroke'];
|
return new RawLayer(layer.get('name'), geoJSONString);
|
||||||
|
|
||||||
const geoJSONString = writer.writeFeatures(features,{featureProjection: EPSG.openLayersDefault});
|
|
||||||
return new RawLayer(layer.get('name'), geoJSONString, rawLayerColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a VectorLayer instance from a RawLayer and applies the layer color to all features
|
* Creates a VectorLayer instance from a RawLayer and applies the layer or custom color to all features
|
||||||
* @param rawLayer
|
* @param rawLayer
|
||||||
*/
|
*/
|
||||||
public static toVectorLayer(rawLayer: RawLayer): VectorLayer {
|
public static toVectorLayer(rawLayer: RawLayer): VectorLayer {
|
||||||
const vectorSource = new VectorSource();
|
const vectorSource = new VectorSource();
|
||||||
const geoJSON = new GeoJSON();
|
const features = RawLayer.geoJSONToFeatures(rawLayer.data);
|
||||||
// load geoJSON features from rawLayer
|
|
||||||
const features = geoJSON.readFeatures(rawLayer.data, {featureProjection: EPSG.openLayersDefault});
|
|
||||||
|
|
||||||
// apply style to all features
|
// apply style to all features
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
// TODO
|
const featureCustomProperties = RawLayer.getCustomProperties(feature);
|
||||||
// determine if this feature has a own color! If so, do not apply default color and use own color.
|
feature.setStyle(this.determineColorStyle(featureCustomProperties));
|
||||||
const featureProperties = feature.getProperties();
|
});
|
||||||
const strokeColor = featureProperties[Config.GEO_JSON_PREFIX]['default-style-stroke'];
|
|
||||||
const fillColor = featureProperties[Config.GEO_JSON_PREFIX]['default-style-fill'];
|
|
||||||
|
|
||||||
feature.setStyle(new Style({
|
|
||||||
stroke: new Stroke({
|
|
||||||
color: strokeColor,
|
|
||||||
width: 1
|
|
||||||
}),
|
|
||||||
fill: new Fill({
|
|
||||||
color: fillColor
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
|
|
||||||
// add features to source
|
// add features to source
|
||||||
vectorSource.addFeatures(features);
|
vectorSource.addFeatures(features);
|
||||||
@@ -87,20 +60,98 @@ export class RawLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the RawLayer color property to all feature properties from data attribute
|
* Creates the features from geoJSON
|
||||||
*/
|
*/
|
||||||
private saveColorToGeoJSON() {
|
private static geoJSONToFeatures(geoJSON: string): Feature[] {
|
||||||
const geoJSON = new GeoJSON();
|
return new GeoJSON().readFeatures(geoJSON, {featureProjection: EPSG.openLayersDefault});
|
||||||
// load geoJSON features from rawLayer
|
}
|
||||||
const features = geoJSON.readFeatures(this.data, {featureProjection: EPSG.openLayersDefault});
|
|
||||||
|
/**
|
||||||
|
* Creates geoJSON string from features
|
||||||
|
* @param features
|
||||||
|
*/
|
||||||
|
private static featuresToGeoJSON(features: Feature[]): string {
|
||||||
|
return new GeoJSON().writeFeatures(features, {featureProjection: EPSG.openLayersDefault});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves arbitrary key value pairs to geo json representation of this layer as custom properties
|
||||||
|
* @param feature
|
||||||
|
* @param properties
|
||||||
|
*/
|
||||||
|
public static saveCustomProperties(feature: Feature, properties: { [key: string]: any }) {
|
||||||
|
const newProperties = {};
|
||||||
|
// consider custom properties that were saved before
|
||||||
|
const savedProperties = RawLayer.getCustomProperties(feature);
|
||||||
|
newProperties[Config.GEO_JSON_PREFIX] = {...savedProperties, ...properties};
|
||||||
|
feature.setProperties(newProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all custom properties of this layer from its geo json representation
|
||||||
|
* @param feature
|
||||||
|
*/
|
||||||
|
public static getCustomProperties(feature: Feature): { [key: string]: any } {
|
||||||
|
const featureProperties = feature.getProperties();
|
||||||
|
return featureProperties[Config.GEO_JSON_PREFIX];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which color style should be applied. If no custom color property is provided, the default
|
||||||
|
* color is used.
|
||||||
|
* @param featureCustomProperties
|
||||||
|
*/
|
||||||
|
public static determineColorStyle(featureCustomProperties: { [key: string]: any }): Style {
|
||||||
|
if (featureCustomProperties && featureCustomProperties[CustomProperties.COLOR_STROKE]) {
|
||||||
|
return new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: featureCustomProperties[CustomProperties.COLOR_STROKE],
|
||||||
|
width: 1
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: featureCustomProperties[CustomProperties.COLOR_FILL]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else if (featureCustomProperties && featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE]) {
|
||||||
|
return new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE],
|
||||||
|
width: 1
|
||||||
|
}),
|
||||||
|
fill: new Fill({
|
||||||
|
color: featureCustomProperties[CustomProperties.DEFAULT_COLOR_FILL]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('no color properties are defined');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a default color property to geoJSON
|
||||||
|
*/
|
||||||
|
public saveDefaultColor(color: number[]) {
|
||||||
|
const features = RawLayer.geoJSONToFeatures(this.data);
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
// save default color in feature properties
|
// save default color in feature properties
|
||||||
const stylesToSave = {};
|
const properties = {};
|
||||||
stylesToSave[Config.GEO_JSON_PREFIX] = {};
|
properties[CustomProperties.DEFAULT_COLOR_STROKE] = color;
|
||||||
stylesToSave[Config.GEO_JSON_PREFIX]['default-style-stroke'] = this.color;
|
properties[CustomProperties.DEFAULT_COLOR_FILL] = [...color.slice(0, 3), Config.DEFAULT_FILL_OPACITY_TO_STROKE];
|
||||||
stylesToSave[Config.GEO_JSON_PREFIX]['default-style-fill'] = [...this.color.slice(0, 3), 0.1];
|
RawLayer.saveCustomProperties(feature, properties);
|
||||||
feature.setProperties(stylesToSave);
|
});
|
||||||
})
|
this.data = RawLayer.featuresToGeoJSON(features);
|
||||||
this.data = geoJSON.writeFeatures(features, {featureProjection: EPSG.openLayersDefault});
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the default color property from geoJSON
|
||||||
|
*/
|
||||||
|
public getDefaultColor(): number[] {
|
||||||
|
const features = RawLayer.geoJSONToFeatures(this.data);
|
||||||
|
const featureCustomProperties = RawLayer.getCustomProperties(features[0]);
|
||||||
|
if (featureCustomProperties && featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE]) {
|
||||||
|
return featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>GaneWeb</title>
|
<title>Life Map Web</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ module.exports = function (config) {
|
|||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
},
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
dir: require('path').join(__dirname, '../coverage/gane-web'),
|
dir: require('path').join(__dirname, '../coverage/lm-web'),
|
||||||
reports: ['html', 'lcovonly', 'text-summary'],
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
fixWebpackSourcePaths: true
|
fixWebpackSourcePaths: true
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user