Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ Changelog
=========

## current
* feat: add custom topic filter ([#86])

[#86]: https://github.com/GIScience/ohsome-dashboard/issues/86

## 1.9.0

Expand Down
1,727 changes: 717 additions & 1,010 deletions src/app/oqapi/oqt-api-metadata.response.mock.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,34 @@
}
}

/* dropdown search with pre defined attributes + EDIT button*/
.editable-topic {
display: flex;
align-items: flex-start;
flex: 1;
}

.editable-topic > div.field {
flex: 1 1 auto;
}

.editable-topic > button {
flex: 0 0 auto;
margin-left: 1rem;
}

#topic-details {
transition: box-shadow 0.5s;
}

.highlight-topic-details {
/*outline: 2px solid #2185D0;*/

Check warning on line 51 in src/app/oqapi/query-form/oqt-api-query-form/oqt-api-query-form.component.css

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this commented out code.

See more on https://sonarcloud.io/project/issues?id=GIScience_ohsome-dashboard&issues=AZ39ZwlUkzfUE8jhvhJ4&open=AZ39ZwlUkzfUE8jhvhJ4&pullRequest=87
/*outline-offset: 5px;*/

Check warning on line 52 in src/app/oqapi/query-form/oqt-api-query-form/oqt-api-query-form.component.css

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this commented out code.

See more on https://sonarcloud.io/project/issues?id=GIScience_ohsome-dashboard&issues=AZ39ZwlUkzfUE8jhvhJ5&open=AZ39ZwlUkzfUE8jhvhJ5&pullRequest=87
box-shadow:
0 0 0 5px #f3f4f5,
0 0 0 7px #2185D0;
}

.quality-dimension {
text-transform: capitalize;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,81 @@
<!-- 1. Topic or AOI-->
<h4 id="topicHeading" class="ui header" i18n><i class="filter icon"></i>Topic</h4>
<button type="button" id="searchTopicBtn" class="ui right floated basic button" tabindex="0"
(click)="stateService.updatePartialState({showWelcomeScreen: true, welcomeTab: 'topicCatalog'})" i18n>
(click)="stateService.updatePartialState({showWelcomeScreen: true, welcomeTab: 'topicCatalog'})" i18n>
<i class="icon search"></i>
Search Topic Catalog
</button>
<div class="field">
<app-sui-multi-select-search-dropdown #searchSelectTopic required
[multiple]="false"
name="topic"
[(ngModel)]="selectedTopicKey"
[options]="{fullTextSearch: 'exact'}"
[selectOptions]="topics | keyvalue"></app-sui-multi-select-search-dropdown>
</div>

<div class="ui basic segment">
<div class="ui items">
<div class="ui items">
<div class="item">
<div class="editable-topic">
<div class="field">
<app-sui-multi-select-search-dropdown #searchSelectTopic required
[multiple]="false"
name="topic"
[(ngModel)]="selectedTopicKey"
[options]="{fullTextSearch: 'exact'}"
[selectOptions]="topics | keyvalue">
</app-sui-multi-select-search-dropdown>
</div>
<button type="button" class="ui blue icon button"
data-tooltip="Edit the topic filter."
data-inverted
[class.disabled]="selectedTopicKey === 'custom-topic'"
(click)="setCustomTopic()"
i18n-data-tooltip
>
<i class="edit icon"></i>
</button>
</div>
</div>
</div>


<!-- <div class="ui basic segment">-->

Check warning on line 42 in src/app/oqapi/query-form/oqt-api-query-form/oqt-api-query-form.component.html

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this commented out code.

See more on https://sonarcloud.io/project/issues?id=GIScience_ohsome-dashboard&issues=AZ39ZwnvkzfUE8jhvhJ6&open=AZ39ZwnvkzfUE8jhvhJ6&pullRequest=87
<div class="ui items" id="topic-details" [class.highlight-topic-details]="selectedTopicKey === 'custom-topic'">
<div class="item">
<div class="content">
{{ topics[selectedTopicKey].description }}
</div>
</div>
@if (selectedTopicKey === 'custom-topic') {
<div class="item">
<div class="content">
<label for="topic-title">Custom topic title:</label>
<input type="text" id="topic-title"
name="topic-title"
placeholder="Enter your Topic Title"
[ngModel]="topicTitleDefinition()"
(ngModelChange)="setCustomTopicTitleDefinition($event)"
required
i18n-placeholder
>
</div>
</div>
}
<div class="item">
<div class="content" i18n>
<a href="https://docs.ohsome.org/ohsome-api/v1/filter.html" target="_blank">ohsome filter</a> definition
of the topic:
<app-prism-editor id="topicFilter" [readonly]="true" [value]="this.topics[selectedTopicKey].filter"
[partialSetupOptions]="{wordWrap: true, lineNumbers: false}"
[backgroundColor]="'#f9f9f9'"
></app-prism-editor>
<div class="content">
<span i18n><a href="https://docs.ohsome.org/ohsome-api/v1/filter.html" target="_blank">ohsome filter</a> definition
of the topic:</span>
@if (selectedTopicKey === 'custom-topic') {
<app-prism-editor id="topicFilter" [readonly]="false"
[partialSetupOptions]="{wordWrap: true, lineNumbers: true}"
[value]="topicFilterDefinition()"
(valueChange)="setCustomTopicFilterDefinition($event)"
></app-prism-editor>
<input type="hidden" id="topic-filter"
name="topic-filter"
[ngModel]="topicFilterDefinition()"
required>
} @else {
<app-prism-editor id="topicFilter" [readonly]="true" [value]="this.topics[selectedTopicKey].filter"
[partialSetupOptions]="{wordWrap: true, lineNumbers: false}"
[backgroundColor]="'#f9f9f9'"
></app-prism-editor>
}

</div>
</div>
</div>
Expand All @@ -58,7 +106,7 @@
[indicator]="indicator"
[qualityDimension]="qualityDimension"
(indicatorToggle)="onIndicatorToggle()"
>
>
@switch (indicator.key) {
@case ("attribute-completeness") {
@if (indicator.checked) {
Expand Down Expand Up @@ -98,8 +146,8 @@
<!-- Warning if a topic exists, but none of its indicators_ is available -->
@if (currentQualityDimensions.size === 0) {
<div class="ui small negative icon message" i18n>
<i class="minus circle icon"></i>Sorry, currently no indicators are available for this topic.
</div>
<i class="minus circle icon"></i>Sorry, currently no indicators are available for this topic.
</div>
}

</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import {Component, computed, effect, EventEmitter, inject, OnDestroy, OnInit, Output, Renderer2} from '@angular/core';
import { ControlContainer, NgForm, FormsModule } from '@angular/forms';
import {ControlContainer, FormsModule, NgForm} from '@angular/forms';
import {Checkbox, Indicator, RawQualityDimensionMetadata, Topic} from '../../types/types';
import {OqtApiMetadataProviderService} from '../../oqt-api-metadata-provider.service';
import {Userlayer} from '../../../shared/shared-types';
import {StateService} from '../../../singelton-services/state.service';
import {UrlHashParamsProviderService} from '../../../singelton-services/url-hash-params-provider.service';
import { SuiMultiSelectSearchDropdownComponent } from '../../../shared/components/sui-dropdown/sui-multi-select-search-dropdown.component';
import { PrismEditorComponent } from '../../../shared/components/prism-editor/prism-editor.component';
import { SimpleIndicatorComponent } from './simple-indicator/simple-indicator.component';
import { AttributeCompletenessAttributesComponent } from './attribute-completeness-attributes/attribute-completeness-attributes.component';
import { ThematicAccuracyIndicatorComponent } from './thematic-accuracy-indicator/thematic-accuracy-indicator.component';
import { KeyValuePipe } from '@angular/common';
import {
SuiMultiSelectSearchDropdownComponent
} from '../../../shared/components/sui-dropdown/sui-multi-select-search-dropdown.component';
import {PrismEditorComponent} from '../../../shared/components/prism-editor/prism-editor.component';
import {SimpleIndicatorComponent} from './simple-indicator/simple-indicator.component';
import {
AttributeCompletenessAttributesComponent
} from './attribute-completeness-attributes/attribute-completeness-attributes.component';
import {ThematicAccuracyIndicatorComponent} from './thematic-accuracy-indicator/thematic-accuracy-indicator.component';
import {KeyValuePipe} from '@angular/common';
import Utils from '../../../../utils';

@Component({
selector: 'app-oqt-api-query-form',
templateUrl: './oqt-api-query-form.component.html',
styleUrls: ['./oqt-api-query-form.component.css'],
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
selector: 'app-oqt-api-query-form',
templateUrl: './oqt-api-query-form.component.html',
styleUrls: ['./oqt-api-query-form.component.css'],
viewProviders: [{provide: ControlContainer, useExisting: NgForm}],
imports: [FormsModule, SuiMultiSelectSearchDropdownComponent, PrismEditorComponent, SimpleIndicatorComponent, AttributeCompletenessAttributesComponent, KeyValuePipe, ThematicAccuracyIndicatorComponent]
})
export class OqtApiQueryFormComponent implements OnInit, OnDestroy {
Expand All @@ -29,6 +34,7 @@ export class OqtApiQueryFormComponent implements OnInit, OnDestroy {
hashParamsSignal = computed(() => this.urlHashParamsProviderService.currentHashParams());
hashParams = this.hashParamsSignal();


@Output() changeIndicatorCoverages = new EventEmitter<Userlayer[]>()
private indicatorCoverages: Userlayer[] = [];

Expand All @@ -53,43 +59,64 @@ export class OqtApiQueryFormComponent implements OnInit, OnDestroy {
// current quality dimensions to display based on the selected topic
public currentQualityDimensions: Set<string> = new Set();

topicParamSignal = computed(()=> {
// Set values from Permalink
// topic
topicParamSignal = computed(() => {
const topicParam = this.hashParamsSignal().get('topic');
return (topicParam && Object.keys(this.topics).includes(topicParam)) ? topicParam : Object.keys(this.topics)[0];
return (topicParam && Object.keys(this.topics).includes(topicParam)) ? topicParam : Utils.loadEnv('defaultTopicKey',Object.keys(this.topics)[0]) ;
});

// custom topic (title and filter)
topicTitleDefinition = computed(() => {
return this.hashParamsSignal().get('topic-title') ?? '';
});
indicatorsParamSignal = computed(()=> {

topicFilterDefinition = computed(() => {
return this.hashParamsSignal().get('topic-filter') ?? '';
})

// indicators
indicatorsParamSignal = computed(() => {
return this.hashParamsSignal().get('indicators');

});

constructor() {

effect(() => {
console.log("3 topic", this.topicParamSignal())
console.log("1 topic", this.topicParamSignal())
this.selectedTopicKey = this.topicParamSignal();
// on topic change, check if a stored custom topic is available and use it
if (this.selectedTopicKey === "custom-topic") {
const appState = this.stateService.appState()
if (appState.customTopicTitle && appState.customTopicFilter) {
this.urlHashParamsProviderService.updateHashParams({
'topic-title': appState.customTopicTitle,
'topic-filter': appState.customTopicFilter,
})
}
}
});

effect(() => {
console.log("4 indicator", this.indicatorsParamSignal())
console.log("2 indicator", this.indicatorsParamSignal())
this.setIndicators(this.indicatorsParamSignal());
});

// update appState to store custom topic init when coming form url
this.stateService.updatePartialState({
customTopicTitle: this.topicTitleDefinition(),
customTopicFilter: this.topicFilterDefinition()
}
)

}

ngOnInit(): void {
// get metadata and enrich it to fill the form view
this.indicators = this.getEnrichedIndicators();
this.topics = this.getEnrichedTopics(this.indicators);
this.qualityDimensions = structuredClone(this.oqtApiMetadataProviderService.getOqtApiMetadata().result['qualityDimensions']);

// fill form with hash or default values
// set topic
console.log("1 topic", this.hashParams.get('topic'));
const topicParam = this.hashParams.get('topic');
this.selectedTopicKey = (topicParam && Object.keys(this.topics).includes(topicParam)) ? topicParam : Object.keys(this.topics)[0];

//set indicators
console.log("2 indicator", this.hashParams.get('indicators'));
this.setIndicators(this.hashParams.get('indicators'));

}

ngOnDestroy() {
Expand Down Expand Up @@ -205,7 +232,7 @@ export class OqtApiQueryFormComponent implements OnInit, OnDestroy {
);

for (const indicatorKey of checkedIndicators) {
(async ()=>{
(async () => {
const maskedUserLayer = await this.oqtApiMetadataProviderService.getIndicatorCoverage(indicatorKey);
this.indicatorCoverages.push(maskedUserLayer);
this.changeIndicatorCoverages.emit(this.indicatorCoverages);
Expand All @@ -218,4 +245,27 @@ export class OqtApiQueryFormComponent implements OnInit, OnDestroy {
this.updateIndicatorCoverages();
}

setCustomTopicTitleDefinition(title: string) {
this.urlHashParamsProviderService.updateHashParam('topic-title', title);
this.stateService.updatePartialState({'customTopicTitle': title});
}

setCustomTopicFilterDefinition(filter: string) {
this.urlHashParamsProviderService.updateHashParam('topic-filter', filter);
this.stateService.updatePartialState({'customTopicFilter': filter});
this.topics[this.selectedTopicKey].filter = filter;
}

setCustomTopic() {
let customTopic = {
"topic": "custom-topic",
"topic-title": this.topics[this.selectedTopicKey].name,
"topic-filter": this.topics[this.selectedTopicKey].filter
}
this.urlHashParamsProviderService.updateHashParams(customTopic);
this.stateService.updatePartialState({
customTopicTitle: customTopic['topic-title'],
customTopicFilter: customTopic['topic-filter'],
})
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnInit, inject, input } from '@angular/core';
import {FeatureCollection, MultiPolygon, Polygon} from 'geojson';
import {OqtApiService} from '../../oqt-api.service';
import {IndicatorLabel, IndicatorParams, IndicatorResponseJSON} from '../../types/types';
Expand All @@ -23,6 +23,8 @@ export class IndicatorResultComponent implements OnInit {


@Input() topicKey: string;
topicTitle= input<string>()
topicFilter= input<string>()
@Input() bpolys: FeatureCollection<Polygon | MultiPolygon>;// Feature<Polygon | MultiPolygon>;
@Input() indicator!: IndicatorParams;

Expand All @@ -45,13 +47,13 @@ export class IndicatorResultComponent implements OnInit {
indicatorName: string;

ngOnInit(): void {
// add additional request parameters from attributes that have attributeParams like attribite-completeness
// add additional request parameters from attributes that have attributeParams like attribute-completeness
const body = this.prepareRequestBody();
this.getIndicatorResults(body);
}

private prepareRequestBody() {
const body = {
let body = {
topic: this.topicKey,
bpolys: this.bpolys,
}
Expand All @@ -76,6 +78,12 @@ export class IndicatorResultComponent implements OnInit {
})
}

if(this.topicKey === 'custom-topic') {
// delete body.topic
body['topicTitle'] = this.topicTitle();
body['topicFilter'] = this.topicFilter();
}

return body;
}

Expand Down
Loading