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.
|
||||
|
||||
|
||||
+9
-9
@@ -3,7 +3,7 @@
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"gane-web": {
|
||||
"lm-web": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
@@ -17,7 +17,7 @@
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/gane-web",
|
||||
"outputPath": "dist/lm-web",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
@@ -62,18 +62,18 @@
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "gane-web:build"
|
||||
"browserTarget": "lm-web:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "gane-web:build:production"
|
||||
"browserTarget": "lm-web:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "gane-web:build"
|
||||
"browserTarget": "lm-web:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
@@ -107,7 +107,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"gane-web-e2e": {
|
||||
"lm-web-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"prefix": "",
|
||||
@@ -116,11 +116,11 @@
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "gane-web:serve"
|
||||
"devServerTarget": "lm-web:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"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', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to gane-web!');
|
||||
expect(page.getTitleText()).toEqual('Welcome to lm web!');
|
||||
});
|
||||
|
||||
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",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
@@ -11,43 +11,45 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^7.2.12",
|
||||
"@angular/animations": "^7.2.16",
|
||||
"@angular/cdk": "^7.3.7",
|
||||
"@angular/common": "~7.2.0",
|
||||
"@angular/compiler": "~7.2.0",
|
||||
"@angular/core": "~7.2.0",
|
||||
"@angular/forms": "~7.2.0",
|
||||
"@angular/common": "^7.2.16",
|
||||
"@angular/compiler": "^7.2.16",
|
||||
"@angular/core": "^7.2.16",
|
||||
"@angular/forms": "^7.2.16",
|
||||
"@angular/material": "^7.3.7",
|
||||
"@angular/platform-browser": "~7.2.0",
|
||||
"@angular/platform-browser-dynamic": "~7.2.0",
|
||||
"@angular/router": "~7.2.0",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/openlayers": "^4.6.16",
|
||||
"core-js": "^2.5.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
"leaflet": "^1.4.0",
|
||||
"ol": "^5.3.1",
|
||||
"@angular/platform-browser": "^7.2.16",
|
||||
"@angular/platform-browser-dynamic": "^7.2.16",
|
||||
"@angular/router": "^7.2.16",
|
||||
"@types/jszip": "^3.1.7",
|
||||
"@types/openlayers": "^4.6.17",
|
||||
"core-js": "^2.6.11",
|
||||
"file-saver": "^2.0.2",
|
||||
"jszip": "^3.2.2",
|
||||
"ngx-color-picker": "^7.5.0",
|
||||
"ol": "^5.3.3",
|
||||
"rxjs": "~6.3.3",
|
||||
"tslib": "^1.9.0",
|
||||
"stream": "0.0.2",
|
||||
"tslib": "^1.10.0",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.13.0",
|
||||
"@angular/cli": "~7.3.5",
|
||||
"@angular/compiler-cli": "~7.2.0",
|
||||
"@angular/language-service": "~7.2.0",
|
||||
"@angular-devkit/build-angular": "^0.13.0",
|
||||
"@angular/cli": "^7.3.9",
|
||||
"@angular/compiler-cli": "^7.2.16",
|
||||
"@angular/language-service": "^7.2.16",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/node": "~8.9.4",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~4.0.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-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"protractor": "^5.4.3",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.2.2"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material";
|
||||
import {ValidationErrors} from "@angular/forms";
|
||||
import {RawLayer} from "../models/RawLayer";
|
||||
import {Utils} from "../misc/Utils";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material';
|
||||
import {ValidationErrors} from '@angular/forms';
|
||||
import {RawLayer} from '../models/RawLayer';
|
||||
import {Utils} from '../misc/Utils';
|
||||
|
||||
/**
|
||||
* TODO: Add description
|
||||
@@ -24,10 +24,11 @@ export class AddLayerComponent {
|
||||
layerName: '',
|
||||
layerData: null,
|
||||
color: '#4fadc2'
|
||||
}
|
||||
};
|
||||
|
||||
public uploadError: ValidationErrors = null;
|
||||
public uploadIsDirty: boolean = false;
|
||||
public editMode : boolean = false;
|
||||
public editMode: boolean = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<AddLayerComponent>,
|
||||
@@ -36,7 +37,8 @@ export class AddLayerComponent {
|
||||
if (data) {
|
||||
this.formData.layerName = data.name;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +64,7 @@ export class AddLayerComponent {
|
||||
} else {
|
||||
this.uploadError = {
|
||||
invalidFileType: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const fileReader = new FileReader();
|
||||
@@ -75,7 +77,8 @@ export class AddLayerComponent {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import {MapComponent} from "./map/map.component";
|
||||
import {LeafletMapComponent} from "./leaflet-map/leaflet-map.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '**', component: MapComponent},
|
||||
{path: 'osm', component: MapComponent},
|
||||
{path: 'leaflet', component: LeafletMapComponent},
|
||||
{path: 'osm', component: MapComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -20,16 +20,16 @@ describe('AppComponent', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'gane-web'`, () => {
|
||||
it(`should have as title 'lmweb'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
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', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
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 'hammerjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
||||
+29
-17
@@ -1,29 +1,37 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MapComponent } from './map/map.component';
|
||||
import { LeafletMapComponent } from './leaflet-map/leaflet-map.component';
|
||||
import {HttpClient, HttpClientModule} from "@angular/common/http";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import {MatCardModule, MatDialogModule, MatGridListModule, MatInputModule, MatToolbarModule} from "@angular/material";
|
||||
import {DragDropModule} from "@angular/cdk/drag-drop";
|
||||
import { AddLayerComponent } from './add-layer-component/add-layer.component';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {MapComponent} from './map/map.component';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {
|
||||
MatCardModule,
|
||||
MatDialogModule,
|
||||
MatGridListModule,
|
||||
MatInputModule,
|
||||
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({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MapComponent,
|
||||
LeafletMapComponent,
|
||||
AddLayerComponent
|
||||
AddLayerComponent,
|
||||
FeatureManagerComponent,
|
||||
CustomColorEntryComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
MatCardModule,
|
||||
MatGridListModule,
|
||||
@@ -31,10 +39,14 @@ import { AddLayerComponent } from './add-layer-component/add-layer.component';
|
||||
DragDropModule,
|
||||
MatInputModule,
|
||||
MatDialogModule,
|
||||
MatTableModule,
|
||||
MatTooltipModule,
|
||||
ColorPickerModule
|
||||
],
|
||||
entryComponents: [
|
||||
AddLayerComponent
|
||||
],
|
||||
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>
|
||||
<!-- layer property area-->
|
||||
<div class="layer-property-area">
|
||||
<div class="property-list-area">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Layer Properties</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div>
|
||||
{{ currentFeatureInfo | json}}
|
||||
</div>
|
||||
<lm-feature-manager [curFeature]="cache.selectedFeature"></lm-feature-manager>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@@ -32,32 +30,39 @@
|
||||
<div class="layer-control-area">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Layer</mat-card-title>
|
||||
<mat-card-title>Layer
|
||||
</mat-card-title>
|
||||
<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>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div cdkDropList class="layer-list" (cdkDropListDropped)="drop($event)">
|
||||
<div class="list-entry" *ngFor="let layer of customLayerCollection.getArray();let i = index" cdkDrag>
|
||||
<h4>{{layer.get('name') }}</h4>
|
||||
<i class="material-icons" (click)="centerLayerOnMap(layer)">
|
||||
center_focus_strong
|
||||
</i>
|
||||
<i class="material-icons" (click)="toggleLayerVisibility(layer)">
|
||||
{{layer.getVisible() ? 'visibility' : 'visibility_off'}}
|
||||
</i>
|
||||
<i class="material-icons" (click)="editLayer(i)">
|
||||
edit
|
||||
</i>
|
||||
<i class="material-icons" (click)="deleteLayer(layer)">
|
||||
delete
|
||||
</i>
|
||||
<h4 class="title">{{layer.get('name') }}</h4>
|
||||
<div class="layer-controls">
|
||||
<i class="material-icons" (click)="centerLayerOnMap(layer)">
|
||||
center_focus_strong
|
||||
</i>
|
||||
<i class="material-icons" (click)="toggleLayerVisibility(layer)">
|
||||
{{layer.getVisible() ? 'visibility' : 'visibility_off'}}
|
||||
</i>
|
||||
<i class="material-icons" (click)="editLayer(i)">
|
||||
edit
|
||||
</i>
|
||||
<i class="material-icons" (click)="deleteLayer(layer)">
|
||||
delete
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,11 @@
|
||||
}
|
||||
|
||||
.layer-control-area {
|
||||
max-width:25vw;
|
||||
|
||||
.controls {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layer-list {
|
||||
max-width: 100%;
|
||||
@@ -51,18 +55,31 @@
|
||||
|
||||
|
||||
.list-entry {
|
||||
padding: 20px 10px;
|
||||
padding: 10px;
|
||||
border-bottom: solid 1px #ccc;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
cursor: move;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
|
||||
.title {
|
||||
margin: 10px 0px 10px 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.layer-controls {
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
@@ -73,6 +90,10 @@
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.property-list-area {
|
||||
max-width: 25vw;
|
||||
}
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
|
||||
+304
-69
@@ -5,17 +5,33 @@ import TileLayer from 'ol/layer/Tile';
|
||||
import {fromLonLat, transform} from 'ol/proj';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import {OSM} from 'ol/source';
|
||||
import {CdkDragDrop} from "@angular/cdk/drag-drop";
|
||||
import {MatDialog} from "@angular/material";
|
||||
import {AddLayerComponent} from "../add-layer-component/add-layer.component";
|
||||
import {RawLayer} from "../models/RawLayer";
|
||||
import {CdkDragDrop} from '@angular/cdk/drag-drop';
|
||||
import {MatDialog} from '@angular/material';
|
||||
import {AddLayerComponent} from '../add-layer-component/add-layer.component';
|
||||
import {RawLayer} from '../models/RawLayer';
|
||||
import LayerGroup from 'ol/layer/Group';
|
||||
import {Collection} from "openlayers";
|
||||
import {EPSG} from "../enums/EPSG";
|
||||
import {Collection} from 'openlayers';
|
||||
import {EPSG} from '../enums/EPSG';
|
||||
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
|
||||
* - 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({
|
||||
selector: 'app-map',
|
||||
@@ -25,6 +41,7 @@ import {inAndOut} from 'ol/easing';
|
||||
export class MapComponent implements OnInit {
|
||||
|
||||
@ViewChild('mapContainer') mapContainer: ElementRef<HTMLDivElement>;
|
||||
@ViewChild(FeatureManagerComponent) featureManager: FeatureManagerComponent;
|
||||
public map: Map;
|
||||
public baseLayer: TileLayer;
|
||||
|
||||
@@ -46,17 +63,50 @@ export class MapComponent implements OnInit {
|
||||
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) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.initCache();
|
||||
this.initBaseLayer();
|
||||
this.initLayerGroup();
|
||||
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
|
||||
requestAnimationFrame(() => {
|
||||
this.map.updateSize();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all map event listener
|
||||
*/
|
||||
private addMapEventlistener() {
|
||||
private addMapEventListener() {
|
||||
// get current cursor coordinates to transform and display them
|
||||
this.map.on('pointermove', (evt) => {
|
||||
const tmpCoords = transform(evt.coordinate, EPSG.openLayersDefault, EPSG.wgs84);
|
||||
@@ -113,71 +163,92 @@ export class MapComponent implements OnInit {
|
||||
|
||||
// add event to get feature infos
|
||||
this.map.on('pointermove', (evt) => {
|
||||
if (evt.dragging) {
|
||||
if (evt.dragging || this.cache.selectedFeature) {
|
||||
return;
|
||||
}
|
||||
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.
|
||||
* @param pixel
|
||||
* Adds all feature manager event listener
|
||||
*/
|
||||
private displayFeatureInfo(pixel) {
|
||||
const features = [];
|
||||
this.map.forEachFeatureAtPixel(pixel, (feature) => {
|
||||
features.push(feature)
|
||||
}
|
||||
);
|
||||
private addFeatureManagerEventListener() {
|
||||
this.featureManager.featureChangeEmitter.subscribe(() => {
|
||||
// - reset selection values
|
||||
// this.cache.selectedFeature = null;
|
||||
this.cache.lastHighlightedStyle = null;
|
||||
});
|
||||
}
|
||||
|
||||
if (features.length == 0) {
|
||||
this.currentFeatureInfo = 'Too many features. Please zoom in to see feature infos';
|
||||
} else if (features.length == 1) {
|
||||
features.forEach(feature => {
|
||||
const properties = feature.getProperties();
|
||||
delete properties.geometry;
|
||||
this.currentFeatureInfo = properties;
|
||||
})
|
||||
} else {
|
||||
this.currentFeatureInfo = null;
|
||||
/**
|
||||
* Highlights a Features
|
||||
* @param feature
|
||||
*/
|
||||
private highlightFeature(feature: Feature) {
|
||||
// reapply saved feature and style
|
||||
if (this.cache.lastHighlightedFeature) {
|
||||
this.cache.lastHighlightedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||
}
|
||||
};
|
||||
// save current feature and style
|
||||
this.cache.lastHighlightedFeature = feature;
|
||||
this.cache.lastHighlightedStyle = feature.getStyle();
|
||||
|
||||
// private colorPolygons(feature: Feature, resolution: any) {
|
||||
// const styles = {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
// set new feature style
|
||||
feature.setStyle(this.cache.defaultHighlightStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to create a new custom layer
|
||||
*/
|
||||
addLayer(): void {
|
||||
public addLayer(): void {
|
||||
const dialogRef = this.dialog.open(AddLayerComponent);
|
||||
dialogRef.afterClosed().subscribe(rawLayer => {
|
||||
if (rawLayer) {
|
||||
@@ -192,7 +263,7 @@ export class MapComponent implements OnInit {
|
||||
/**
|
||||
* Opens a dialog to edit a custom layer
|
||||
*/
|
||||
editLayer(index: number): void {
|
||||
public editLayer(index: number): void {
|
||||
const vectorLayer = this.customLayerCollection.item(index);
|
||||
const rawLayer = RawLayer.fromVectorLayer(vectorLayer);
|
||||
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.
|
||||
* @param event
|
||||
*/
|
||||
drop(event: CdkDragDrop<RawLayer[]>) {
|
||||
public drop(event: CdkDragDrop<RawLayer[]>) {
|
||||
const tmp = this.customLayerCollection.item(event.previousIndex);
|
||||
this.customLayerCollection.removeAt(event.previousIndex);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -227,14 +302,51 @@ export class MapComponent implements OnInit {
|
||||
* Toggles the visibility of a custom layer
|
||||
* @param index
|
||||
*/
|
||||
toggleLayerVisibility(layer: VectorLayer) {
|
||||
public toggleLayerVisibility(layer: VectorLayer) {
|
||||
layer.setVisible(!layer.getVisible());
|
||||
}
|
||||
|
||||
saveLayers(): RawLayer[] {
|
||||
/**
|
||||
* Export all Layers in one file for app usage
|
||||
*/
|
||||
public exportLayerForApp(): void {
|
||||
const rawLayers = [];
|
||||
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(),
|
||||
duration: 888,
|
||||
easing: inAndOut
|
||||
}
|
||||
};
|
||||
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{
|
||||
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
|
||||
] : 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 GeoJSON from 'ol/format/GeoJSON';
|
||||
import {Vector as VectorSource} from 'ol/source';
|
||||
import {EPSG} from "../enums/EPSG";
|
||||
import {EPSG} from '../enums/EPSG';
|
||||
import Style from 'ol/style/Style';
|
||||
import Stroke from 'ol/style/Stroke';
|
||||
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
|
||||
@@ -19,15 +21,8 @@ export class RawLayer {
|
||||
/**
|
||||
* Data of Layer (normally in geojson format)
|
||||
*/
|
||||
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();
|
||||
}
|
||||
public data: string
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static fromVectorLayer(layer: VectorLayer): RawLayer {
|
||||
const writer = new GeoJSON();
|
||||
const source = layer.getSource();
|
||||
|
||||
// get default style values
|
||||
const features = source.getFeatures();
|
||||
const featureProperties = features[0].getProperties();
|
||||
const rawLayerColor = featureProperties[Config.GEO_JSON_PREFIX]['default-style-stroke'];
|
||||
|
||||
const geoJSONString = writer.writeFeatures(features,{featureProjection: EPSG.openLayersDefault});
|
||||
return new RawLayer(layer.get('name'), geoJSONString, rawLayerColor);
|
||||
const geoJSONString = RawLayer.featuresToGeoJSON(features);
|
||||
return new RawLayer(layer.get('name'), geoJSONString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static toVectorLayer(rawLayer: RawLayer): VectorLayer {
|
||||
const vectorSource = new VectorSource();
|
||||
const geoJSON = new GeoJSON();
|
||||
// load geoJSON features from rawLayer
|
||||
const features = geoJSON.readFeatures(rawLayer.data, {featureProjection: EPSG.openLayersDefault});
|
||||
|
||||
const features = RawLayer.geoJSONToFeatures(rawLayer.data);
|
||||
// apply style to all features
|
||||
features.forEach(feature => {
|
||||
// TODO
|
||||
// determine if this feature has a own color! If so, do not apply default color and use own color.
|
||||
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
|
||||
})
|
||||
}));
|
||||
})
|
||||
const featureCustomProperties = RawLayer.getCustomProperties(feature);
|
||||
feature.setStyle(this.determineColorStyle(featureCustomProperties));
|
||||
});
|
||||
|
||||
// add features to source
|
||||
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() {
|
||||
const geoJSON = new GeoJSON();
|
||||
// load geoJSON features from rawLayer
|
||||
const features = geoJSON.readFeatures(this.data, {featureProjection: EPSG.openLayersDefault});
|
||||
private static geoJSONToFeatures(geoJSON: string): Feature[] {
|
||||
return new GeoJSON().readFeatures(geoJSON, {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 => {
|
||||
// save default color in feature properties
|
||||
const stylesToSave = {};
|
||||
stylesToSave[Config.GEO_JSON_PREFIX] = {};
|
||||
stylesToSave[Config.GEO_JSON_PREFIX]['default-style-stroke'] = this.color;
|
||||
stylesToSave[Config.GEO_JSON_PREFIX]['default-style-fill'] = [...this.color.slice(0, 3), 0.1];
|
||||
feature.setProperties(stylesToSave);
|
||||
})
|
||||
this.data = geoJSON.writeFeatures(features, {featureProjection: EPSG.openLayersDefault});
|
||||
const properties = {};
|
||||
properties[CustomProperties.DEFAULT_COLOR_STROKE] = color;
|
||||
properties[CustomProperties.DEFAULT_COLOR_FILL] = [...color.slice(0, 3), Config.DEFAULT_FILL_OPACITY_TO_STROKE];
|
||||
RawLayer.saveCustomProperties(feature, properties);
|
||||
});
|
||||
this.data = RawLayer.featuresToGeoJSON(features);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>GaneWeb</title>
|
||||
<title>Life Map Web</title>
|
||||
<base href="/">
|
||||
|
||||
<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
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../coverage/gane-web'),
|
||||
dir: require('path').join(__dirname, '../coverage/lm-web'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user