introduce feature management
This commit is contained in:
@@ -18,13 +18,17 @@ import {
|
|||||||
} from "@angular/material";
|
} from "@angular/material";
|
||||||
import {DragDropModule} from "@angular/cdk/drag-drop";
|
import {DragDropModule} from "@angular/cdk/drag-drop";
|
||||||
import { AddLayerComponent } from './add-layer-component/add-layer.component';
|
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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
MapComponent,
|
MapComponent,
|
||||||
LeafletMapComponent,
|
LeafletMapComponent,
|
||||||
AddLayerComponent
|
AddLayerComponent,
|
||||||
|
FeatureManagerComponent,
|
||||||
|
CustomColorEntryComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
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>
|
||||||
</div>
|
</div>
|
||||||
<!-- layer property area-->
|
<!-- layer property area-->
|
||||||
<div class="layer-property-area">
|
<div>
|
||||||
<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 *ngFor="let item of currentFeatureInfo | keyvalue" class="entry">
|
<lm-feature-manager [curFeature]="cache.selectedFeature"></lm-feature-manager>
|
||||||
{{item.key}}
|
|
||||||
<br>
|
|
||||||
{{item.value}}
|
|
||||||
<div class="controls">
|
|
||||||
<i class="material-icons">edit</i>
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,25 +71,6 @@
|
|||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
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 {
|
.cdk-drag-preview {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {Feature} from 'ol/Feature';
|
|||||||
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 {FeatureManagerComponent} from "../feature-management/feature-manager/feature-manager.component";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: add description
|
* TODO: add description
|
||||||
@@ -29,6 +30,7 @@ import Fill from 'ol/style/Fill';
|
|||||||
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;
|
||||||
|
|
||||||
@@ -49,15 +51,10 @@ 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.
|
|
||||||
*/
|
|
||||||
public currentFeatureInfo: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple cache that holds different Features and styles
|
* 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
|
* Contains the feature that is currently selected
|
||||||
*/
|
*/
|
||||||
@@ -76,12 +73,12 @@ export class MapComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Default style for selected feature
|
* Default style for selected feature
|
||||||
*/
|
*/
|
||||||
defaultSelectedStyle : null,
|
defaultSelectedStyle: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default style for a highlighted feauture
|
* 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);
|
const features = this.map.getFeaturesAtPixel(pixel);
|
||||||
if (features && features.length > 0) {
|
if (features && features.length > 0) {
|
||||||
this.map.getTargetElement().style.cursor = 'pointer';
|
this.map.getTargetElement().style.cursor = 'pointer';
|
||||||
this.displayFeatureInfo(features[0]);
|
this.featureManager.curFeature = features[0];
|
||||||
this.highlightFeature(features[0]);
|
this.highlightFeature(features[0]);
|
||||||
} else {
|
} else {
|
||||||
this.map.getTargetElement().style.cursor = '';
|
this.map.getTargetElement().style.cursor = '';
|
||||||
@@ -168,7 +165,7 @@ export class MapComponent implements OnInit {
|
|||||||
// reset cache for feature and style
|
// reset cache for feature and style
|
||||||
this.cache.lastHighlightedFeature = null;
|
this.cache.lastHighlightedFeature = null;
|
||||||
this.cache.lastHighlightedStyle = null;
|
this.cache.lastHighlightedStyle = null;
|
||||||
this.currentFeatureInfo = null;
|
this.featureManager.curFeature = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,7 +181,7 @@ export class MapComponent implements OnInit {
|
|||||||
this.cache.selectedFeature = null;
|
this.cache.selectedFeature = null;
|
||||||
} else if (features && features.length > 0) {
|
} else if (features && features.length > 0) {
|
||||||
this.map.getTargetElement().style.cursor = '';
|
this.map.getTargetElement().style.cursor = '';
|
||||||
this.displayFeatureInfo(features[0]);
|
this.featureManager.curFeature = features[0];
|
||||||
this.cache.selectedFeature = features[0];
|
this.cache.selectedFeature = features[0];
|
||||||
this.cache.lastHighlightedFeature = null;
|
this.cache.lastHighlightedFeature = null;
|
||||||
features[0].setStyle(this.cache.defaultSelectedStyle);
|
features[0].setStyle(this.cache.defaultSelectedStyle);
|
||||||
@@ -192,16 +189,6 @@ export class MapComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Highlights a Features
|
||||||
* @param feature
|
* @param feature
|
||||||
@@ -416,7 +403,7 @@ export class MapComponent implements OnInit {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
const rawLayer = new RawLayer('Mock Data Layer', JSON.stringify(data));
|
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);
|
const customLayer = RawLayer.toVectorLayer(rawLayer);
|
||||||
this.centerLayerOnMap(customLayer);
|
this.centerLayerOnMap(customLayer);
|
||||||
this.customLayerCollection.push(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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 {Feature} from 'ol/Feature';
|
||||||
|
import {CustomProperties} from "../enums/CustomProperties";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a raw (not processed) map layer
|
* Represents a raw (not processed) map layer
|
||||||
@@ -51,11 +52,11 @@ export class RawLayer {
|
|||||||
if (featureCustomProperties && featureCustomProperties['default-style-stroke']) {
|
if (featureCustomProperties && featureCustomProperties['default-style-stroke']) {
|
||||||
feature.setStyle(new Style({
|
feature.setStyle(new Style({
|
||||||
stroke: new Stroke({
|
stroke: new Stroke({
|
||||||
color: featureCustomProperties['default-style-stroke'],
|
color: featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE],
|
||||||
width: 1
|
width: 1
|
||||||
}),
|
}),
|
||||||
fill: new Fill({
|
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);
|
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 properties = {
|
const properties = {};
|
||||||
'default-style-stroke': color,
|
properties[CustomProperties.DEFAULT_COLOR_STROKE] = color;
|
||||||
'default-style-fill': [...color.slice(0, 3), 0.1]
|
properties[CustomProperties.DEFAULT_COLOR_FILL] = [...color.slice(0, 3), Config.DEFAULT_FILL_OPACITY_TO_STROKE];
|
||||||
};
|
|
||||||
RawLayer.saveCustomProperties(feature, properties);
|
RawLayer.saveCustomProperties(feature, properties);
|
||||||
})
|
})
|
||||||
this.data = RawLayer.featuresToGeoJSON(features);
|
this.data = RawLayer.featuresToGeoJSON(features);
|
||||||
@@ -93,8 +93,8 @@ export class RawLayer {
|
|||||||
public getDefaultColor(): number[] {
|
public getDefaultColor(): number[] {
|
||||||
const features = RawLayer.geoJSONToFeatures(this.data);
|
const features = RawLayer.geoJSONToFeatures(this.data);
|
||||||
const featureCustomProperties = RawLayer.getCustomProperties(features[0]);
|
const featureCustomProperties = RawLayer.getCustomProperties(features[0]);
|
||||||
if (featureCustomProperties && featureCustomProperties['default-style-stroke']) {
|
if (featureCustomProperties && featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE]) {
|
||||||
return featureCustomProperties['default-style-stroke'];
|
return featureCustomProperties[CustomProperties.DEFAULT_COLOR_STROKE];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user