You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2020/03/06 23:31:50 UTC
[incubator-streampipes] branch dev updated: STREAMPIPES-58: Add map
widget
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new 79a3f73 STREAMPIPES-58: Add map widget
79a3f73 is described below
commit 79a3f7339e5a0ed433b472b491feac0ddedacf99
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Sat Mar 7 00:31:37 2020 +0100
STREAMPIPES-58: Add map widget
---
ui/angular.json | 7 +-
ui/package.json | 3 +
ui/src/app/connect/model/Option.ts | 7 +-
.../model/RuntimeResolvableAnyStaticProperty.ts | 4 +-
.../model/RuntimeResolvableOneOfStaticProperty.ts | 4 +-
.../app/connect/model/SelectionStaticProperty.ts | 3 +-
.../static-property-util.service.ts | 8 +-
.../widget/dashboard-widget.component.html | 6 +
.../widget/dashboard-widget.component.ts | 3 +-
.../widgets/base/base-ngx-charts-widget.ts | 36 +-----
.../components/widgets/base/base-widget.ts | 41 ++++++-
.../components/widgets/map/map-config.ts | 47 ++++++++
.../widgets/map/map-widget.component.css | 31 ++++++
.../widgets/map/map-widget.component.html | 28 +++++
.../components/widgets/map/map-widget.component.ts | 122 +++++++++++++++++++++
.../widgets/number/number-widget.component.ts | 8 +-
.../widgets/table/table-widget.component.ts | 8 +-
ui/src/app/dashboard-v2/dashboard.module.ts | 6 +-
.../add-visualization-dialog.component.ts | 2 +-
.../dashboard-v2/registry/widget-config-builder.ts | 35 ++++--
.../app/dashboard-v2/registry/widget-registry.ts | 4 +-
ui/src/app/dashboard-v2/sdk/ep-requirements.ts | 9 ++
.../sdk/extractor/static-property-extractor.ts | 11 ++
ui/src/app/dashboard-v2/sdk/model/vocabulary.ts | 1 +
ui/src/scss/main.scss | 1 +
25 files changed, 378 insertions(+), 57 deletions(-)
diff --git a/ui/angular.json b/ui/angular.json
index 8605685..86a210e 100644
--- a/ui/angular.json
+++ b/ui/angular.json
@@ -18,7 +18,12 @@
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
- "src/assets"
+ "src/assets",
+ {
+ "glob": "**/*",
+ "input": "node_modules/leaflet/dist/images",
+ "output": "assets/img"
+ }
],
"styles": [
"src/scss/main.scss"
diff --git a/ui/package.json b/ui/package.json
index 5215d7c..37a251a 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -29,6 +29,7 @@
"@angular/platform-browser-dynamic": "9.0.1",
"@angular/router": "9.0.1",
"@angular/upgrade": "9.0.1",
+ "@asymmetrik/ngx-leaflet": "^6.0.1",
"@fortawesome/fontawesome-free": "^5.12.1",
"@ngui/datetime-picker": "0.16.2",
"@stomp/ng2-stompjs": "^7.2.0",
@@ -79,6 +80,7 @@
"jsplumb": "2.1.3",
"jszip": "^3.2.1",
"konva": "^3.2.4",
+ "leaflet": "^1.6.0",
"lodash": "3.10.1",
"ng-dynamic-component": "4.0.3",
"ng-file-upload": "9.0.13",
@@ -110,6 +112,7 @@
"@types/angular": "^1.6.43",
"@types/jasmine": "~2.8.3",
"@types/jqueryui": "^1.12.7",
+ "@types/leaflet": "^1.5.9",
"@types/node": "^12.11.1",
"@types/rx": "^4.1.1",
"codelyzer": "^5.1.2",
diff --git a/ui/src/app/connect/model/Option.ts b/ui/src/app/connect/model/Option.ts
index ff70873..5042495 100644
--- a/ui/src/app/connect/model/Option.ts
+++ b/ui/src/app/connect/model/Option.ts
@@ -19,9 +19,10 @@
import {RdfId} from '../../platform-services/tsonld/RdfId';
import {RdfProperty} from '../../platform-services/tsonld/RdfsProperty';
import {RdfsClass} from '../../platform-services/tsonld/RdfsClass';
+import {UnnamedStreamPipesEntity} from "./UnnamedStreamPipesEntity";
@RdfsClass('sp:Option')
-export class Option {
+export class Option extends UnnamedStreamPipesEntity {
@RdfId
public id: string;
@@ -36,4 +37,8 @@ export class Option {
@RdfProperty('sp:isSelected')
public selected: boolean;
+
+ constructor() {
+ super();
+ }
}
diff --git a/ui/src/app/connect/model/RuntimeResolvableAnyStaticProperty.ts b/ui/src/app/connect/model/RuntimeResolvableAnyStaticProperty.ts
index 479ec6b..88b27b5 100644
--- a/ui/src/app/connect/model/RuntimeResolvableAnyStaticProperty.ts
+++ b/ui/src/app/connect/model/RuntimeResolvableAnyStaticProperty.ts
@@ -26,7 +26,7 @@ export class RuntimeResolvableAnyStaticProperty extends AnyStaticProperty {
@RdfProperty('sp:dependsOnStaticProperty')
public dependsOn: string[] = [];
- constructor(id: string) {
- super(id);
+ constructor() {
+ super();
}
}
\ No newline at end of file
diff --git a/ui/src/app/connect/model/RuntimeResolvableOneOfStaticProperty.ts b/ui/src/app/connect/model/RuntimeResolvableOneOfStaticProperty.ts
index 838e19b..3dbd6b1 100644
--- a/ui/src/app/connect/model/RuntimeResolvableOneOfStaticProperty.ts
+++ b/ui/src/app/connect/model/RuntimeResolvableOneOfStaticProperty.ts
@@ -26,8 +26,8 @@ export class RuntimeResolvableOneOfStaticProperty extends OneOfStaticProperty {
@RdfProperty('sp:dependsOnStaticProperty')
public dependsOn: string[] = [];
- constructor(id: string) {
- super(id);
+ constructor() {
+ super();
}
diff --git a/ui/src/app/connect/model/SelectionStaticProperty.ts b/ui/src/app/connect/model/SelectionStaticProperty.ts
index 65e490d..8745836 100644
--- a/ui/src/app/connect/model/SelectionStaticProperty.ts
+++ b/ui/src/app/connect/model/SelectionStaticProperty.ts
@@ -37,8 +37,7 @@ export class SelectionStaticProperty extends StaticProperty {
@RdfProperty('sp:isHorizontalRendering')
public horizontalRendering: boolean = true;
- constructor(id: string) {
+ constructor() {
super();
- this.id = id;
}
}
diff --git a/ui/src/app/connect/static-properties/static-property-util.service.ts b/ui/src/app/connect/static-properties/static-property-util.service.ts
index f34348c..b85fbd9 100644
--- a/ui/src/app/connect/static-properties/static-property-util.service.ts
+++ b/ui/src/app/connect/static-properties/static-property-util.service.ts
@@ -107,9 +107,10 @@ export class StaticPropertyUtilService{
}
//SelectionStaticProperty
else if (val instanceof RuntimeResolvableAnyStaticProperty || val instanceof RuntimeResolvableOneOfStaticProperty){
- val instanceof RuntimeResolvableAnyStaticProperty ? clone = new RuntimeResolvableAnyStaticProperty(id) :
- clone = new RuntimeResolvableOneOfStaticProperty(id);
+ val instanceof RuntimeResolvableAnyStaticProperty ? clone = new RuntimeResolvableAnyStaticProperty() :
+ clone = new RuntimeResolvableOneOfStaticProperty();
+ clone.id = id;
clone.dependsOn = val.dependsOn;
clone.value = val.value;
clone.requiredDomainProperty = val.requiredDomainProperty;
@@ -117,8 +118,9 @@ export class StaticPropertyUtilService{
clone.horizontalRendering = val.horizontalRendering;
}
else if (val instanceof AnyStaticProperty || val instanceof OneOfStaticProperty){
- val instanceof AnyStaticProperty ? clone = new AnyStaticProperty(id) : clone = new OneOfStaticProperty(id);
+ val instanceof AnyStaticProperty ? clone = new AnyStaticProperty() : clone = new OneOfStaticProperty();
+ clone.id = id;
clone.value = val.value;
clone.requiredDomainProperty = val.requiredDomainProperty;
clone.options = val.options.map(option => this.cloneOption(option));
diff --git a/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.html b/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.html
index 89d8f01..b667b28 100644
--- a/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.html
+++ b/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.html
@@ -64,6 +64,12 @@
[widgetConfig]="configuredWidget"
[widgetDataConfig]="widgetDataConfig" class="h-100"></image-widget>
</div>
+ <div *ngIf="widget.widgetType === 'map'" class="h-100 p-0">
+ <map-widget [gridsterItemComponent]="gridsterItemComponent"
+ [gridsterItem]="item" [widget]="widget" [editMode]="editMode"
+ [widgetConfig]="configuredWidget"
+ [widgetDataConfig]="widgetDataConfig" class="h-100"></map-widget>
+ </div>
</div>
</div>
</div>
diff --git a/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.ts b/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.ts
index f2994bb..042f4df 100644
--- a/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.ts
+++ b/ui/src/app/dashboard-v2/components/widget/dashboard-widget.component.ts
@@ -68,7 +68,8 @@ export class DashboardWidgetComponent implements OnInit {
height: '500px',
panelClass: 'custom-dialog-container',
data: {
- "widget": this.configuredWidget
+ "widget": this.configuredWidget,
+ "pipeline": this.widgetDataConfig
}
});
diff --git a/ui/src/app/dashboard-v2/components/widgets/base/base-ngx-charts-widget.ts b/ui/src/app/dashboard-v2/components/widgets/base/base-ngx-charts-widget.ts
index 619bd3d..883576d 100644
--- a/ui/src/app/dashboard-v2/components/widgets/base/base-ngx-charts-widget.ts
+++ b/ui/src/app/dashboard-v2/components/widgets/base/base-ngx-charts-widget.ts
@@ -29,8 +29,8 @@ export abstract class BaseNgxChartsStreamPipesWidget extends BaseStreamPipesWidg
colorScheme: any;
- constructor(rxStompService: RxStompService, protected resizeService: ResizeService) {
- super(rxStompService);
+ constructor(rxStompService: RxStompService, resizeService: ResizeService) {
+ super(rxStompService, resizeService, true);
}
ngOnInit() {
@@ -39,36 +39,12 @@ export abstract class BaseNgxChartsStreamPipesWidget extends BaseStreamPipesWidg
this.view = [this.computeCurrentWidth(this.gridsterItemComponent),
this.computeCurrentHeight(this.gridsterItemComponent)];
this.displayChart = true;
- this.resizeService.resizeSubject.subscribe(info => {
- this.onResize(info);
- });
}
- onResize(info: GridsterInfo) {
- if (info.gridsterItem.id === this.gridsterItem.id) {
- setTimeout(() => {
- this.displayChart = false;
- this.view = [this.computeCurrentWidth(info.gridsterItemComponent),
- this.computeCurrentHeight(info.gridsterItemComponent)];
- this.displayChart = true;
- }, 100);
- }
- }
-
- computeCurrentWidth(gridsterItemComponent: GridsterItemComponent): number {
- return (gridsterItemComponent.width - (BaseNgxChartsStreamPipesWidget.PADDING * 2));
- }
-
- computeCurrentHeight(gridsterItemComponent: GridsterItemComponent): number {
- return (gridsterItemComponent.height - (BaseNgxChartsStreamPipesWidget.PADDING * 2) - this.editModeOffset() - this.titlePanelOffset());
- }
-
- editModeOffset(): number {
- return this.editMode ? BaseNgxChartsStreamPipesWidget.EDIT_HEADER_HEIGHT : 0;
- }
-
- titlePanelOffset(): number {
- return this.hasTitlePanelSettings ? 20 : 0;
+ protected onSizeChanged(width: number, height: number) {
+ this.displayChart = false;
+ this.view = [width, height];
+ this.displayChart = true;
}
}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/base/base-widget.ts b/ui/src/app/dashboard-v2/components/widgets/base/base-widget.ts
index 340bc3d..7b2e057 100644
--- a/ui/src/app/dashboard-v2/components/widgets/base/base-widget.ts
+++ b/ui/src/app/dashboard-v2/components/widgets/base/base-widget.ts
@@ -26,6 +26,8 @@ import {Subscription} from "rxjs";
import {GridsterItem, GridsterItemComponent} from "angular-gridster2";
import {WidgetConfigBuilder} from "../../../registry/widget-config-builder";
import {VisualizablePipeline} from "../../../../core-model/dashboard/VisualizablePipeline";
+import {ResizeService} from "../../../services/resize.service";
+import {GridsterInfo} from "../../../models/gridster-info.model";
export abstract class BaseStreamPipesWidget implements OnChanges {
@@ -53,11 +55,17 @@ export abstract class BaseStreamPipesWidget implements OnChanges {
defaultPrimaryTextColor: string = "#FFFFFF";
defaultSecondaryTextColor: string = "#39B54A";
- protected constructor(private rxStompService: RxStompService) {
+
+ protected constructor(private rxStompService: RxStompService,
+ protected resizeService: ResizeService,
+ protected adjustPadding: boolean) {
}
ngOnInit(): void {
this.prepareConfigExtraction();
+ this.resizeService.resizeSubject.subscribe(info => {
+ this.onResize(info);
+ });
this.subscription = this.rxStompService.watch("/topic/" +this.widgetDataConfig.topic).subscribe((message: Message) => {
this.onEvent(JSON.parse(message.body));
});
@@ -86,13 +94,44 @@ export abstract class BaseStreamPipesWidget implements OnChanges {
this.subscription.unsubscribe();
}
+ computeCurrentWidth(gridsterItemComponent: GridsterItemComponent): number {
+ return this.adjustPadding ?
+ (gridsterItemComponent.width - (BaseStreamPipesWidget.PADDING * 2)) :
+ gridsterItemComponent.width;
+ }
+
+ computeCurrentHeight(gridsterItemComponent: GridsterItemComponent): number {
+ return this.adjustPadding ?
+ (gridsterItemComponent.height - (BaseStreamPipesWidget.PADDING * 2) - this.editModeOffset() - this.titlePanelOffset()) :
+ gridsterItemComponent.height - this.editModeOffset() - this.titlePanelOffset();
+ }
+
+ editModeOffset(): number {
+ return this.editMode ? BaseStreamPipesWidget.EDIT_HEADER_HEIGHT : 0;
+ }
+
+ titlePanelOffset(): number {
+ return this.hasTitlePanelSettings ? 20 : 0;
+ }
+
protected abstract extractConfig(extractor: StaticPropertyExtractor);
protected abstract onEvent(event: any);
+ protected abstract onSizeChanged(width: number, height: number);
+
ngOnChanges(changes: SimpleChanges): void {
if (changes["widgetConfig"]) {
this.prepareConfigExtraction();
}
}
+
+ onResize(info: GridsterInfo) {
+ if (info.gridsterItem.id === this.gridsterItem.id) {
+ setTimeout(() => {
+ this.onSizeChanged(this.computeCurrentWidth(info.gridsterItemComponent),
+ this.computeCurrentHeight(info.gridsterItemComponent))
+ }, 100);
+ }
+ }
}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/map/map-config.ts b/ui/src/app/dashboard-v2/components/widgets/map/map-config.ts
new file mode 100644
index 0000000..3545886
--- /dev/null
+++ b/ui/src/app/dashboard-v2/components/widgets/map/map-config.ts
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {WidgetConfigBuilder} from "../../../registry/widget-config-builder";
+import {SchemaRequirementsBuilder} from "../../../sdk/schema-requirements-builder";
+import {EpRequirements} from "../../../sdk/ep-requirements";
+import {DashboardWidgetSettings} from "../../../../core-model/dashboard/DashboardWidgetSettings";
+import {WidgetConfig} from "../base/base-config";
+
+export class MapConfig extends WidgetConfig {
+
+ static readonly LATITUDE_MAPPING_KEY: string = "latitude-mapping";
+ static readonly LONGITUDE_MAPPING_KEY: string = "longitude-mapping";
+ static readonly ITEMS_MAPPING_KEY: string = "items-mapping";
+ static readonly MARKER_TYPE_KEY: string = "marker-type-mapping";
+
+ constructor() {
+ super();
+ }
+
+ getConfig(): DashboardWidgetSettings {
+ return WidgetConfigBuilder.createWithSelectableColorsAndTitlePanel("map", "map")
+ .requiredSchema(SchemaRequirementsBuilder
+ .create()
+ .requiredPropertyWithUnaryMapping(MapConfig.LATITUDE_MAPPING_KEY, "Latitude field", "", EpRequirements.latitudeReq())
+ .requiredPropertyWithUnaryMapping(MapConfig.LONGITUDE_MAPPING_KEY, "Latitude field", "", EpRequirements.longitudeReq())
+ .requiredPropertyWithNaryMapping(MapConfig.ITEMS_MAPPING_KEY, "Fields to display", "", EpRequirements.anyProperty())
+ .build())
+ .requiredSingleValueSelection(MapConfig.MARKER_TYPE_KEY, "Marker type", "", ["Default", "Car"])
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.css b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.css
new file mode 100644
index 0000000..53ea75a
--- /dev/null
+++ b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.css
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.main-panel {
+ width:100%;
+ height: 100%;
+ display:inline-grid;
+ align-content: center;
+}
+
+.mt-20 {
+ margin: 10px;
+}
+
+.title-panel {
+ font-size:20px;
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.html b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.html
new file mode 100644
index 0000000..d159168
--- /dev/null
+++ b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.html
@@ -0,0 +1,28 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<div fxFlex="100" fxLayoutAlign="center center" fxLayout="column" class="main-panel" [ngStyle]="{'background-color': selectedBackgroundColor, 'color': selectedPrimaryTextColor}">
+ <div class="title-panel mt-20" *ngIf="hasTitlePanelSettings">
+ {{selectedTitle}}
+ </div>
+ <div [ngStyle]="{'width': mapWidth + 'px', 'height': mapHeight + 'px'}"
+ leaflet
+ [leafletOptions]="options"
+ (leafletMapReady)="onMapReady($event)">
+ <div *ngIf="showMarkers" [leafletLayer]="markerLayer"></div>
+ </div>
+</div>
diff --git a/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.ts b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.ts
new file mode 100644
index 0000000..61da91c
--- /dev/null
+++ b/ui/src/app/dashboard-v2/components/widgets/map/map-widget.component.ts
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Component, OnDestroy, OnInit} from "@angular/core";
+import {RxStompService} from "@stomp/ng2-stompjs";
+import {BaseStreamPipesWidget} from "../base/base-widget";
+import {StaticPropertyExtractor} from "../../../sdk/extractor/static-property-extractor";
+import {MapConfig} from "./map-config";
+import {latLng, marker, Marker, tileLayer, Map, LatLngExpression, LatLng, icon, Content} from "leaflet";
+import {ResizeService} from "../../../services/resize.service";
+
+@Component({
+ selector: 'map-widget',
+ templateUrl: './map-widget.component.html',
+ styleUrls: ['./map-widget.component.css']
+})
+export class MapWidgetComponent extends BaseStreamPipesWidget implements OnInit, OnDestroy {
+
+ item: any;
+
+ selectedLatitudeField: string;
+ selectedLongitudeField: string;
+ selectedMarkerIcon: string;
+ additionalItemsToDisplay: Array<string>;
+
+ map: Map;
+ showMarkers: boolean = false;
+ markerLayer: Marker;
+
+ mapWidth: number;
+ mapHeight: number;
+
+ options = {
+ layers: [
+ tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: "© <a href=\'https://www.openstreetmap.org/copyright\'>OpenStreetMap</a> Contributors" })
+ ],
+ zoom: 5,
+ center: latLng(46.879966, -121.726909)
+ };
+
+ constructor(rxStompService: RxStompService, resizeService: ResizeService) {
+ super(rxStompService, resizeService, false);
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.markerLayer = this.makeMarker([0, 0]);
+ this.showMarkers = true;
+ this.mapWidth = this.computeCurrentWidth(this.gridsterItemComponent);
+ this.mapHeight = this.computeCurrentHeight(this.gridsterItemComponent);
+ }
+
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ }
+
+ extractConfig(extractor: StaticPropertyExtractor) {
+ this.selectedLatitudeField = extractor.mappingPropertyValue(MapConfig.LATITUDE_MAPPING_KEY);
+ this.selectedLongitudeField = extractor.mappingPropertyValue(MapConfig.LONGITUDE_MAPPING_KEY);
+ this.selectedMarkerIcon = this.markerImage(extractor.selectedSingleValue(MapConfig.MARKER_TYPE_KEY));
+ this.additionalItemsToDisplay = extractor.mappingPropertyValues(MapConfig.ITEMS_MAPPING_KEY);
+ }
+
+ markerImage(selectedMarker: string): string {
+ return selectedMarker === "Default" ? 'assets/img/marker-icon.png' : 'assets/img/pe_icons/car.png';
+ }
+
+ onMapReady(map: Map) {
+ this.map = map;
+ this.map.invalidateSize();
+ }
+
+ protected onEvent(event: any) {
+ this.updatePosition(event);
+ }
+
+ updatePosition(event) {
+ var lat = event[this.selectedLatitudeField];
+ var long = event[this.selectedLongitudeField];
+ var text = "";
+ this.additionalItemsToDisplay.forEach(item => {
+ text = text.concat("<b>" +item +"</b>" + ": " + event[item] + "<br>");
+ });
+
+ let content : Content = text;
+ let point: LatLngExpression = new LatLng(lat, long);
+ this.markerLayer.setLatLng(point);
+ this.markerLayer.bindTooltip(content);
+ this.map.panTo(point);
+
+ };
+
+ makeMarker(point: LatLngExpression): Marker {
+ return marker(point, { icon: icon({
+ iconSize: [ 25, 41 ],
+ iconAnchor: [ 13, 41 ],
+ iconUrl: this.selectedMarkerIcon,
+ shadowUrl: 'assets/img/marker-shadow.png'
+ })});
+ }
+
+ protected onSizeChanged(width: number, height: number) {
+ this.mapWidth = width;
+ this.mapHeight = height;
+ this.map.invalidateSize();
+ }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/number/number-widget.component.ts b/ui/src/app/dashboard-v2/components/widgets/number/number-widget.component.ts
index 75421c9..28e1373 100644
--- a/ui/src/app/dashboard-v2/components/widgets/number/number-widget.component.ts
+++ b/ui/src/app/dashboard-v2/components/widgets/number/number-widget.component.ts
@@ -21,6 +21,7 @@ import {RxStompService} from "@stomp/ng2-stompjs";
import {BaseStreamPipesWidget} from "../base/base-widget";
import {StaticPropertyExtractor} from "../../../sdk/extractor/static-property-extractor";
import {NumberConfig} from "./number-config";
+import {ResizeService} from "../../../services/resize.service";
@Component({
selector: 'number-widget',
@@ -33,8 +34,8 @@ export class NumberWidgetComponent extends BaseStreamPipesWidget implements OnIn
selectedProperty: string;
- constructor(rxStompService: RxStompService) {
- super(rxStompService);
+ constructor(rxStompService: RxStompService, resizeService: ResizeService) {
+ super(rxStompService, resizeService, false);
}
ngOnInit(): void {
@@ -57,4 +58,7 @@ export class NumberWidgetComponent extends BaseStreamPipesWidget implements OnIn
this.item = event[this.selectedProperty];
}
+ protected onSizeChanged(width: number, height: number) {
+ }
+
}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/components/widgets/table/table-widget.component.ts b/ui/src/app/dashboard-v2/components/widgets/table/table-widget.component.ts
index e6e37aa..17185f2 100644
--- a/ui/src/app/dashboard-v2/components/widgets/table/table-widget.component.ts
+++ b/ui/src/app/dashboard-v2/components/widgets/table/table-widget.component.ts
@@ -23,6 +23,7 @@ import {StaticPropertyExtractor} from "../../../sdk/extractor/static-property-ex
import {MatTableDataSource} from "@angular/material/table";
import {TableConfig} from "./table-config";
import {SemanticTypeUtilsService} from "../../../../core-services/semantic-type/semantic-type-utils.service";
+import {ResizeService} from "../../../services/resize.service";
@Component({
selector: 'table-widget',
@@ -37,8 +38,8 @@ export class TableWidgetComponent extends BaseStreamPipesWidget implements OnIni
dataSource = new MatTableDataSource();
semanticTypes: { [key: string]: string; } = {};
- constructor(rxStompService: RxStompService, private semanticTypeUtils: SemanticTypeUtilsService) {
- super(rxStompService);
+ constructor(rxStompService: RxStompService, resizeService: ResizeService, private semanticTypeUtils: SemanticTypeUtilsService) {
+ super(rxStompService, resizeService, false);
}
ngOnInit(): void {
@@ -74,4 +75,7 @@ export class TableWidgetComponent extends BaseStreamPipesWidget implements OnIni
return object;
}
+ protected onSizeChanged(width: number, height: number) {
+ }
+
}
\ No newline at end of file
diff --git a/ui/src/app/dashboard-v2/dashboard.module.ts b/ui/src/app/dashboard-v2/dashboard.module.ts
index 5aed5d1..5d806e9 100644
--- a/ui/src/app/dashboard-v2/dashboard.module.ts
+++ b/ui/src/app/dashboard-v2/dashboard.module.ts
@@ -49,6 +49,8 @@ import {SemanticTypeUtilsService} from '../core-services/semantic-type/semantic-
import {GaugeWidgetComponent} from "./components/widgets/gauge/gauge-widget.component";
import {ImageWidgetComponent} from "./components/widgets/image/image-widget.component";
import {AreaWidgetComponent} from "./components/widgets/area/area-widget.component";
+import {MapWidgetComponent} from "./components/widgets/map/map-widget.component";
+import {LeafletModule} from "@asymmetrik/ngx-leaflet";
const dashboardWidgets = [
@@ -72,6 +74,7 @@ const dashboardWidgets = [
ConnectModule,
NgxChartsModule,
CdkTableModule,
+ LeafletModule
],
declarations: [
DashboardComponent,
@@ -86,7 +89,8 @@ const dashboardWidgets = [
NumberWidgetComponent,
TableWidgetComponent,
GaugeWidgetComponent,
- ImageWidgetComponent
+ ImageWidgetComponent,
+ MapWidgetComponent
],
providers: [
DashboardService,
diff --git a/ui/src/app/dashboard-v2/dialogs/add-widget/add-visualization-dialog.component.ts b/ui/src/app/dashboard-v2/dialogs/add-widget/add-visualization-dialog.component.ts
index 58ec6ad..8ea4895 100644
--- a/ui/src/app/dashboard-v2/dialogs/add-widget/add-visualization-dialog.component.ts
+++ b/ui/src/app/dashboard-v2/dialogs/add-widget/add-visualization-dialog.component.ts
@@ -81,7 +81,7 @@ export class AddVisualizationDialogComponent {
this.availableWidgets = WidgetRegistry.getAvailableWidgetTemplates();
} else {
this.dialogTitle = "Edit widget";
- this.selectedPipeline = this.data.widget.dashboardWidgetDataConfig;
+ this.selectedPipeline = this.data.pipeline;
this.selectedWidget = this.data.widget.dashboardWidgetSettings;
this.page = 'configure-widget';
}
diff --git a/ui/src/app/dashboard-v2/registry/widget-config-builder.ts b/ui/src/app/dashboard-v2/registry/widget-config-builder.ts
index ed9a5f8..e7911a4 100644
--- a/ui/src/app/dashboard-v2/registry/widget-config-builder.ts
+++ b/ui/src/app/dashboard-v2/registry/widget-config-builder.ts
@@ -21,6 +21,9 @@ import {CollectedSchemaRequirements} from "../sdk/collected-schema-requirements"
import {DashboardWidgetSettings} from "../../core-model/dashboard/DashboardWidgetSettings";
import {Datatypes} from "../sdk/model/datatypes";
import {ColorPickerStaticProperty} from "../../connect/model/ColorPickerStaticProperty";
+import {OneOfStaticProperty} from "../../connect/model/OneOfStaticProperty";
+import {StaticProperty} from "../../connect/model/StaticProperty";
+import {Option} from "../../connect/model/Option";
export class WidgetConfigBuilder {
@@ -64,7 +67,7 @@ export class WidgetConfigBuilder {
}
requiredTextParameter(id: string, label: string, description: string): WidgetConfigBuilder {
- let fst: FreeTextStaticProperty = this.prepareStaticProperty(id, label, description, Datatypes.String.toUri())
+ let fst: FreeTextStaticProperty = this.prepareFreeTextStaticProperty(id, label, description, Datatypes.String.toUri())
this.widget.config.push(fst);
return this;
}
@@ -83,13 +86,27 @@ export class WidgetConfigBuilder {
requiredIntegerParameter(id: string, label: string, description: string): WidgetConfigBuilder {
- let fst: FreeTextStaticProperty = this.prepareStaticProperty(id, label, description, Datatypes.Integer.toUri())
+ let fst: FreeTextStaticProperty = this.prepareFreeTextStaticProperty(id, label, description, Datatypes.Integer.toUri())
this.widget.config.push(fst);
return this;
}
+ requiredSingleValueSelection(id: string, label: string, description: string, options: Array<string>): WidgetConfigBuilder {
+ let osp: OneOfStaticProperty = new OneOfStaticProperty();
+ this.prepareStaticProperty(id, label, description, osp);
+
+ osp.options = [];
+ options.forEach(o => {
+ let option = new Option();
+ option.name = o;
+ osp.options.push(option);
+ });
+ this.widget.config.push(osp);
+ return this;
+ }
+
requiredFloatParameter(id: string, label: string, description: string): WidgetConfigBuilder {
- let fst: FreeTextStaticProperty = this.prepareStaticProperty(id, label, description, Datatypes.Float.toUri())
+ let fst: FreeTextStaticProperty = this.prepareFreeTextStaticProperty(id, label, description, Datatypes.Float.toUri())
this.widget.config.push(fst);
return this;
}
@@ -101,11 +118,15 @@ export class WidgetConfigBuilder {
return this;
}
- prepareStaticProperty(id: string, label: string, description: string, datatype: string) {
+ prepareStaticProperty(id: string, label: string, description: string, sp: StaticProperty) {
+ sp.internalName = id;
+ sp.label = label;
+ sp.description = description;
+ }
+
+ prepareFreeTextStaticProperty(id: string, label: string, description: string, datatype: string) {
let fst: FreeTextStaticProperty = new FreeTextStaticProperty();
- fst.internalName = id;
- fst.label = label;
- fst.description = description;
+ this.prepareStaticProperty(id, label, description, fst);
fst.requiredDatatype = datatype;
return fst;
diff --git a/ui/src/app/dashboard-v2/registry/widget-registry.ts b/ui/src/app/dashboard-v2/registry/widget-registry.ts
index 3c62fe6..c00daf2 100644
--- a/ui/src/app/dashboard-v2/registry/widget-registry.ts
+++ b/ui/src/app/dashboard-v2/registry/widget-registry.ts
@@ -24,6 +24,7 @@ import {TableConfig} from "../components/widgets/table/table-config";
import {GaugeConfig} from "../components/widgets/gauge/gauge-config";
import {ImageConfig} from "../components/widgets/image/image-config";
import {AreaConfig} from "../components/widgets/area/area-config";
+import {MapConfig} from "../components/widgets/map/map-config";
export class WidgetRegistry {
@@ -33,7 +34,8 @@ export class WidgetRegistry {
new TableConfig(),
new GaugeConfig(),
new ImageConfig(),
- new AreaConfig()
+ new AreaConfig(),
+ new MapConfig()
];
static getAvailableWidgetTemplates(): Array<DashboardWidgetSettings> {
diff --git a/ui/src/app/dashboard-v2/sdk/ep-requirements.ts b/ui/src/app/dashboard-v2/sdk/ep-requirements.ts
index b298a7a..5a2174f 100644
--- a/ui/src/app/dashboard-v2/sdk/ep-requirements.ts
+++ b/ui/src/app/dashboard-v2/sdk/ep-requirements.ts
@@ -19,6 +19,7 @@
import {EventProperty} from "../../connect/schema-editor/model/EventProperty";
import {EventPropertyPrimitive} from "../../connect/schema-editor/model/EventPropertyPrimitive";
import {Datatypes} from "./model/datatypes";
+import {Vocabulary} from "./model/vocabulary";
export class EpRequirements {
@@ -35,6 +36,14 @@ export class EpRequirements {
return EpRequirements.domainPropertyReq("https://image.com");
}
+ static latitudeReq(): EventProperty {
+ return EpRequirements.domainPropertyReq(Vocabulary.GEO + "lat");
+ }
+
+ static longitudeReq(): EventProperty {
+ return EpRequirements.domainPropertyReq(Vocabulary.GEO + "long")
+ }
+
static timestampReq(): EventProperty {
return EpRequirements.domainPropertyReq("http://schema.org/DateTime");
}
diff --git a/ui/src/app/dashboard-v2/sdk/extractor/static-property-extractor.ts b/ui/src/app/dashboard-v2/sdk/extractor/static-property-extractor.ts
index 93d0adb..5d3c143 100644
--- a/ui/src/app/dashboard-v2/sdk/extractor/static-property-extractor.ts
+++ b/ui/src/app/dashboard-v2/sdk/extractor/static-property-extractor.ts
@@ -22,6 +22,7 @@ import {MappingPropertyUnary} from "../../../connect/model/MappingPropertyUnary"
import {FreeTextStaticProperty} from "../../../connect/model/FreeTextStaticProperty";
import {ColorPickerStaticProperty} from "../../../connect/model/ColorPickerStaticProperty";
import {MappingPropertyNary} from "../../../connect/model/MappingPropertyNary";
+import {OneOfStaticProperty} from "../../../connect/model/OneOfStaticProperty";
export class StaticPropertyExtractor {
@@ -42,6 +43,11 @@ export class StaticPropertyExtractor {
mappingPropertyValues(internalId: string): Array<string> {
let sp: MappingPropertyNary = this.getStaticPropertyByName(internalId) as MappingPropertyNary;
let properties: Array<string> = [];
+ // TODO this quick-fixes a deserialization bug in Tson-LD
+ if (!Array.isArray(sp.selectedProperties)) {
+ let value: string = sp.selectedProperties as any;
+ sp.selectedProperties = [value];
+ }
sp.selectedProperties.forEach(ep => {
properties.push(this.removePrefix(ep));
});
@@ -58,6 +64,11 @@ export class StaticPropertyExtractor {
return sp.selectedColor;
}
+ selectedSingleValue(internalId: string): string {
+ let sp: OneOfStaticProperty = this.getStaticPropertyByName(internalId) as OneOfStaticProperty;
+ return sp.options.find(o => o.selected).name;
+ }
+
stringParameter(internalId: string): string {
return this.singleValueParameter(internalId) as string;
}
diff --git a/ui/src/app/dashboard-v2/sdk/model/vocabulary.ts b/ui/src/app/dashboard-v2/sdk/model/vocabulary.ts
index 175377b..90c0c9c 100644
--- a/ui/src/app/dashboard-v2/sdk/model/vocabulary.ts
+++ b/ui/src/app/dashboard-v2/sdk/model/vocabulary.ts
@@ -20,4 +20,5 @@ export class Vocabulary {
static readonly XSD: string = "http://www.w3.org/2001/XMLSchema#";
static readonly SO: string = "http://schema.org/";
+ static readonly GEO: string = "http://www.w3.org/2003/01/geo/wgs84_pos#"
}
\ No newline at end of file
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 9200d4f..34a9567 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -35,6 +35,7 @@
@import '~prismjs/themes/prism.css';
@import '~angular-loading-bar/build/loading-bar.min.css';
@import '~shepherd.js/dist/css/shepherd-theme-default.css';
+@import '~leaflet/dist/leaflet.css';
@import '../assets/fonts/MaterialIcons-Regular.css';
@import '../assets/fonts/Roboto-Regular.css';