introduce feature management
This commit is contained in:
@@ -18,13 +18,17 @@ import {
|
||||
} 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";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MapComponent,
|
||||
LeafletMapComponent,
|
||||
AddLayerComponent
|
||||
AddLayerComponent,
|
||||
FeatureManagerComponent,
|
||||
CustomColorEntryComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* An enum that presents all custom property keys
|
||||
*/
|
||||
export enum CustomProperties {
|
||||
DEFAULT_COLOR_STROKE = 'default-style-stroke',
|
||||
DEFAULT_COLOR_FILL = 'default-style-fill'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="entry">
|
||||
Default Color:
|
||||
<div class="color-preview" [ngStyle]="{
|
||||
'background-color': fillColorAsRgba,
|
||||
'border-color': strokeColorAsRgba
|
||||
}"></div>
|
||||
<div class="controls">
|
||||
<i class="material-icons" (click)="startEditDialog()">edit</i>
|
||||
<i class="material-icons">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-left: .5rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border: 2px solid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {Component, EventEmitter, Input, Output} from "@angular/core";
|
||||
import {CustomProperties} from "../../enums/CustomProperties";
|
||||
import {Utils} from "../../misc/Utils";
|
||||
|
||||
/**
|
||||
* 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 property.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'lm-custom-color-entry',
|
||||
templateUrl: 'custom-color-entry.component.html',
|
||||
styleUrls: ['custom-color-entry.component.scss']
|
||||
})
|
||||
export class CustomColorEntryComponent {
|
||||
|
||||
|
||||
_colors: any;
|
||||
@Input() set colors(value: any) {
|
||||
this._colors = value;
|
||||
}
|
||||
|
||||
get colors(): any {
|
||||
return this._colors;
|
||||
}
|
||||
|
||||
get strokeColor(): number[] {
|
||||
return this.colors[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||
}
|
||||
|
||||
get strokeColorAsRgba(): string {
|
||||
return Utils.colorArrayToRgba(this.strokeColor);
|
||||
}
|
||||
|
||||
get fillColor(): number[] {
|
||||
return this.colors[CustomProperties.DEFAULT_COLOR_FILL];
|
||||
}
|
||||
|
||||
get fillColorAsRgba(): string {
|
||||
return Utils.colorArrayToRgba(this.fillColor);
|
||||
}
|
||||
|
||||
@Output('colorChanged') colorChangeEmitter: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
startEditDialog() {
|
||||
this.colorChangeEmitter.emit(this.colors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<div *ngIf="curFeature">
|
||||
<div class="header">
|
||||
<h4>Properties</h4>
|
||||
</div>
|
||||
<div *ngFor="let item of curProperties | keyvalue" class="entry">
|
||||
{{item.key}}
|
||||
<br>
|
||||
{{item.value}}
|
||||
<div class="controls">
|
||||
<i class="material-icons">edit</i>
|
||||
<i class="material-icons">delete</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<h4>Custom Properties</h4>
|
||||
</div>
|
||||
<!--<div *ngFor="let item of curCustomProperties | keyvalue" class="entry">-->
|
||||
<!-- {{item.key}}-->
|
||||
<!-- <br>-->
|
||||
<!-- {{item.value}}-->
|
||||
<!-- <div class="controls">-->
|
||||
<!-- <i class="material-icons">edit</i>-->
|
||||
<!-- <i class="material-icons">delete</i>-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
<lm-custom-color-entry [colors]="curCustomDefaultColor"
|
||||
(colorChanged)="defaultColorChanged($event)"></lm-custom-color-entry>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
.header,
|
||||
.entry {
|
||||
display: flex;
|
||||
height: 3rem;
|
||||
align-items: center;
|
||||
margin: 0 0 .125rem 0;
|
||||
border: 1px black solid;
|
||||
padding: .5rem;
|
||||
|
||||
.controls {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.header{
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {Component, Input} from "@angular/core";
|
||||
// import {Feature} from 'ol/Feature';
|
||||
import {Config} from "../../misc/Config";
|
||||
import {CustomProperties} from "../../enums/CustomProperties";
|
||||
import {Feature} from 'ol/Feature';
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 curCustomDefaultColor(): 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];
|
||||
return colors;
|
||||
}
|
||||
|
||||
defaultColorChanged(colors: any) {
|
||||
console.log('Default colors changed!', colors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- layer property area-->
|
||||
<div class="layer-property-area">
|
||||
<div>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Layer Properties</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div *ngFor="let item of currentFeatureInfo | keyvalue" class="entry">
|
||||
{{item.key}}
|
||||
<br>
|
||||
{{item.value}}
|
||||
<div class="controls">
|
||||
<i class="material-icons">edit</i>
|
||||
<i class="material-icons">delete</i>
|
||||
</div>
|
||||
</div>
|
||||
<lm-feature-manager [curFeature]="cache.selectedFeature"></lm-feature-manager>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
@@ -71,25 +71,6 @@
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-property-area {
|
||||
|
||||
.mat-card-content {
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
height: 3rem;
|
||||
align-items: center;
|
||||
margin: 0 0 .125rem 0;
|
||||
border: 1px black solid;
|
||||
padding: .5rem;
|
||||
|
||||
.controls {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
|
||||
@@ -17,6 +17,7 @@ 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";
|
||||
|
||||
/**
|
||||
* TODO: add description
|
||||
@@ -29,6 +30,7 @@ import Fill from 'ol/style/Fill';
|
||||
export class MapComponent implements OnInit {
|
||||
|
||||
@ViewChild('mapContainer') mapContainer: ElementRef<HTMLDivElement>;
|
||||
@ViewChild(FeatureManagerComponent) featureManager: FeatureManagerComponent;
|
||||
public map: Map;
|
||||
public baseLayer: TileLayer;
|
||||
|
||||
@@ -49,15 +51,10 @@ export class MapComponent implements OnInit {
|
||||
*/
|
||||
public cursorCoordinates: string;
|
||||
|
||||
/**
|
||||
* The properties of the current feature. The current layer is the layer beneath the cursor.
|
||||
*/
|
||||
public currentFeatureInfo: any;
|
||||
|
||||
/**
|
||||
* A simple cache that holds different Features and styles
|
||||
*/
|
||||
private cache : { [key: string]: Feature | Style } = {
|
||||
private cache: { [key: string]: Feature | Style } = {
|
||||
/**
|
||||
* Contains the feature that is currently selected
|
||||
*/
|
||||
@@ -76,12 +73,12 @@ export class MapComponent implements OnInit {
|
||||
/**
|
||||
* Default style for selected feature
|
||||
*/
|
||||
defaultSelectedStyle : null,
|
||||
defaultSelectedStyle: null,
|
||||
|
||||
/**
|
||||
* Default style for a highlighted feauture
|
||||
*/
|
||||
defaultHighlightStyle:null
|
||||
defaultHighlightStyle: null
|
||||
};
|
||||
|
||||
|
||||
@@ -157,7 +154,7 @@ export class MapComponent implements OnInit {
|
||||
const features = this.map.getFeaturesAtPixel(pixel);
|
||||
if (features && features.length > 0) {
|
||||
this.map.getTargetElement().style.cursor = 'pointer';
|
||||
this.displayFeatureInfo(features[0]);
|
||||
this.featureManager.curFeature = features[0];
|
||||
this.highlightFeature(features[0]);
|
||||
} else {
|
||||
this.map.getTargetElement().style.cursor = '';
|
||||
@@ -168,7 +165,7 @@ export class MapComponent implements OnInit {
|
||||
// reset cache for feature and style
|
||||
this.cache.lastHighlightedFeature = null;
|
||||
this.cache.lastHighlightedStyle = null;
|
||||
this.currentFeatureInfo = null;
|
||||
this.featureManager.curFeature = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -177,31 +174,21 @@ export class MapComponent implements OnInit {
|
||||
const pixel = this.map.getEventPixel(evt.originalEvent);
|
||||
const features = this.map.getFeaturesAtPixel(pixel);
|
||||
|
||||
if (this.cache.selectedFeature) {
|
||||
// use lastHighlightedStyle because the original style is saved there
|
||||
this.cache.selectedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||
// this.styleCache['lastHighlightedStyle'] = null;
|
||||
this.cache.selectedFeature = null;
|
||||
} else if (features && features.length > 0) {
|
||||
this.map.getTargetElement().style.cursor = '';
|
||||
this.displayFeatureInfo(features[0]);
|
||||
this.cache.selectedFeature = features[0];
|
||||
this.cache.lastHighlightedFeature = null;
|
||||
features[0].setStyle(this.cache.defaultSelectedStyle);
|
||||
}
|
||||
if (this.cache.selectedFeature) {
|
||||
// use lastHighlightedStyle because the original style is saved there
|
||||
this.cache.selectedFeature.setStyle(this.cache.lastHighlightedStyle);
|
||||
// this.styleCache['lastHighlightedStyle'] = null;
|
||||
this.cache.selectedFeature = null;
|
||||
} else if (features && features.length > 0) {
|
||||
this.map.getTargetElement().style.cursor = '';
|
||||
this.featureManager.curFeature = features[0];
|
||||
this.cache.selectedFeature = features[0];
|
||||
this.cache.lastHighlightedFeature = null;
|
||||
features[0].setStyle(this.cache.defaultSelectedStyle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepared feature properties and shows them
|
||||
* @param features
|
||||
*/
|
||||
private displayFeatureInfo(feature: Feature) {
|
||||
const properties = feature.getProperties();
|
||||
delete properties.geometry;
|
||||
this.currentFeatureInfo = properties;
|
||||
};
|
||||
|
||||
/**
|
||||
* Highlights a Features
|
||||
* @param feature
|
||||
@@ -416,7 +403,7 @@ export class MapComponent implements OnInit {
|
||||
]
|
||||
};
|
||||
const rawLayer = new RawLayer('Mock Data Layer', JSON.stringify(data));
|
||||
rawLayer.saveDefaultColor([125,125,125])
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Stroke from 'ol/style/Stroke';
|
||||
import Fill from 'ol/style/Fill';
|
||||
import {Config} from "../misc/Config";
|
||||
import {Feature} from 'ol/Feature';
|
||||
import {CustomProperties} from "../enums/CustomProperties";
|
||||
|
||||
/**
|
||||
* Represents a raw (not processed) map layer
|
||||
@@ -51,11 +52,11 @@ export class RawLayer {
|
||||
if (featureCustomProperties && featureCustomProperties['default-style-stroke']) {
|
||||
feature.setStyle(new Style({
|
||||
stroke: new Stroke({
|
||||
color: featureCustomProperties['default-style-stroke'],
|
||||
color: featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE],
|
||||
width: 1
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: featureCustomProperties['default-style-fill']
|
||||
color: featureCustomProperties[CustomProperties.DEFAULT_COLOR_FILL]
|
||||
})
|
||||
}));
|
||||
}
|
||||
@@ -78,10 +79,9 @@ export class RawLayer {
|
||||
const features = RawLayer.geoJSONToFeatures(this.data);
|
||||
features.forEach(feature => {
|
||||
// save default color in feature properties
|
||||
const properties = {
|
||||
'default-style-stroke': color,
|
||||
'default-style-fill': [...color.slice(0, 3), 0.1]
|
||||
};
|
||||
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);
|
||||
@@ -93,8 +93,8 @@ export class RawLayer {
|
||||
public getDefaultColor(): number[] {
|
||||
const features = RawLayer.geoJSONToFeatures(this.data);
|
||||
const featureCustomProperties = RawLayer.getCustomProperties(features[0]);
|
||||
if (featureCustomProperties && featureCustomProperties['default-style-stroke']) {
|
||||
return featureCustomProperties['default-style-stroke'];
|
||||
if (featureCustomProperties && featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE]) {
|
||||
return featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user