File

services/app.service.ts

Description

Entrypoint to store for GM vs distance application.

Extends

SharedService

Index

Properties
Methods
Accessors

Constructor

constructor(formBuilder: FormBuilder, nshmpService: NshmpService, spinnerService: SpinnerService, route: ActivatedRoute, location: LocationService)
Parameters :
Name Type Optional
formBuilder FormBuilder No
nshmpService NshmpService No
spinnerService SpinnerService No
route ActivatedRoute No
location LocationService No

Methods

addValidators
addValidators()

Add validators to form controls.

Returns : void
callService
callService()

Calls the Gmm distance service.

Returns : void
createPlots
createPlots()

Create plots based on current state and form group.

Returns : void
defaultFormValues
defaultFormValues()

Default values for control panel.

Returns : FormControls
defaultPlots
defaultPlots()

Returns the default plots.

Returns : Map<string, NshmpPlot>
Private handleServiceResponses
handleServiceResponses(serviceResponses: GmmDistanceResponse[])
Parameters :
Name Type Optional
serviceResponses GmmDistanceResponse[] No
Returns : void
Private handleUsageResponse
handleUsageResponse(usageResponse: GmmDistanceUsage)
Parameters :
Name Type Optional
usageResponse GmmDistanceUsage No
Returns : void
init
init()

Initialize the application.

Returns : void
Private initialFormSet
initialFormSet()
Returns : void
initialState
initialState()

GMM distance app inital state

Returns : AppState
resetControlPanel
resetControlPanel()

Reset the control panel.

Returns : void
resetPlotSettings
resetPlotSettings()

Reset the plot settings.

Returns : void
resetState
resetState()

Reset the state.

Returns : void
Private serviceResponseToPlotData
serviceResponseToPlotData(state: AppState, form: FormGroupControls<FormControls>)

Transform Gmm distance service responses to plots.

Parameters :
Name Type Optional Description
state AppState No

The application state

form FormGroupControls<FormControls> No
Returns : Map<string, NshmpPlot>
updateState
updateState(state: Partial<AppState>)

Update state.

Parameters :
Name Type Optional Description
state Partial<AppState> No

Partial new state to update

Returns : void
Private updateUrl
updateUrl()
Returns : void
usageFormValues
usageFormValues(parameters: GmmDistanceUsageParameters)

Returns the default from values from parameters.

Parameters :
Name Type Optional Description
parameters GmmDistanceUsageParameters No

The service parameters

Returns : FormControls

Properties

Private baseUrl
Default value : environment.webServices.data.url

nshmp-ws base URL

Readonly formGroup
Default value : this.formBuilder.group({ ...this.defaultFormValues(), gmmSource: [], MwMulti: [], vs30Multi: [], }) as FormGroupControls<FormControls>
Private serviceUrl
Default value : `${this.baseUrl}${environment.webServices.data.services.gmmDistance}`

GMM service URL

Readonly state
Default value : signal<AppState>(this.initialState())

Accessors

meanPlotState
getmeanPlotState()

Returns the mean plot state.

Returns : Signal<NshmpPlot>
serviceCallInfo
getserviceCallInfo()
serviceResponse
getserviceResponse()

Returns the Gmm distance service responses.

Returns : Signal<GmmDistanceResponse[]>
supportedImts
getsupportedImts()
usage
getusage()

Returns the Gmm distance usage response.

Returns : Signal<GmmDistanceUsage>
import {Location as LocationService} from '@angular/common';
import {HttpParams} from '@angular/common/http';
import {computed, Injectable, Signal, signal} from '@angular/core';
import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
import {ActivatedRoute} from '@angular/router';
import {
  GmmAppQueryImt,
  gmmUtils,
  MultiSelectableParam,
} from '@ghsc/nshmp-lib-ng/gmm';
import {
  FormGroupControls,
  NshmpService,
  nshmpUtils,
  ServiceCallInfo,
  SpinnerService,
} from '@ghsc/nshmp-lib-ng/nshmp';
import {NshmpPlot, NshmpPlotSettings, plotUtils} from '@ghsc/nshmp-lib-ng/plot';
import {XySequence} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data';
import {Imt, imtToString} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm';
import {
  GmmDistanceResponse,
  GmmDistanceUsage,
  GmmDistanceUsageParameters,
  GmmGroupType,
} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
import {environment} from 'projects/nshmp-apps/src/environments/environment';
import {AppServiceModel} from 'projects/nshmp-apps/src/shared/models/app-service.model';
import {SharedService} from 'projects/nshmp-apps/src/shared/services/shared.service';
import {apps} from 'projects/nshmp-apps/src/shared/utils/applications.utils';
import {catchError} from 'rxjs';

import {FormControls} from '../models/form-controls.model';
import {AppState} from '../models/state.model';

interface Query extends GmmAppQueryImt {
  /** Max distance */
  rMax: string;
  /** Min distance */
  rMin: string;
}

/**
 * Entrypoint to store for GM vs distance application.
 */
@Injectable({
  providedIn: 'root',
})
export class AppService
  extends SharedService
  implements AppServiceModel<AppState, FormControls>
{
  /** nshmp-ws base URL */
  private baseUrl = environment.webServices.data.url;
  /** GMM service URL */
  private serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmDistance}`;

  readonly formGroup = this.formBuilder.group({
    ...this.defaultFormValues(),
    gmmSource: [],
    MwMulti: [],
    vs30Multi: [],
  }) as FormGroupControls<FormControls>;

  readonly state = signal<AppState>(this.initialState());

  constructor(
    private formBuilder: FormBuilder,
    private nshmpService: NshmpService,
    private spinnerService: SpinnerService,
    private route: ActivatedRoute,
    private location: LocationService,
  ) {
    super();
    this.addValidators();
    this.formGroup.controls.gmmSource.setValue([]);
    this.formGroup.controls.showEpistemicUncertainty.disable();
  }

  /**
   * Returns the mean plot state.
   */
  get meanPlotState(): Signal<NshmpPlot> {
    return computed(() => this.state().plots?.get(gmmUtils.PlotType.MEANS));
  }

  get serviceCallInfo(): Signal<ServiceCallInfo> {
    return computed(() => this.state().serviceCallInfo);
  }

  /**
   * Returns the Gmm distance service responses.
   */
  get serviceResponse(): Signal<GmmDistanceResponse[]> {
    return computed(() => this.state().serviceResponses);
  }

  get supportedImts(): Signal<EnumParameterValues[]> {
    return computed(() => this.state().supportedImts);
  }

  /**
   * Returns the Gmm distance usage response.
   */
  get usage(): Signal<GmmDistanceUsage> {
    return computed(() => this.state().usageResponse);
  }

  /**
   * Add validators to form controls.
   *
   * @param form The form group
   */
  addValidators(): void {
    const required = (control: AbstractControl) => Validators.required(control);

    this.formGroup.controls.Mw.addValidators(required);
    this.formGroup.controls.dip.addValidators(required);
    this.formGroup.controls.gmmSource.addValidators(required);
    this.formGroup.controls.imt.addValidators(required);
    this.formGroup.controls.multiSelectableParam.addValidators(required);
    this.formGroup.controls.vs30.addValidators(required);
    this.formGroup.controls.width.addValidators(required);
    this.formGroup.controls.zTor.addValidators(required);

    this.formGroup.controls.MwMulti.addValidators(control => {
      if (
        this.formGroup.getRawValue().multiSelectableParam ===
        MultiSelectableParam.MW
      ) {
        return Validators.required(control);
      } else {
        return Validators.nullValidator(control);
      }
    });

    this.formGroup.controls.vs30Multi.addValidators(control => {
      if (
        this.formGroup.getRawValue().multiSelectableParam ===
        MultiSelectableParam.VS30
      ) {
        return Validators.required(control);
      } else {
        return Validators.nullValidator(control);
      }
    });

    this.formGroup.updateValueAndValidity();
  }

  /**
   * Calls the Gmm distance service.
   */
  callService(): void {
    if (this.formGroup.invalid) {
      return;
    }

    const spinnerRef = this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);

    const urls = gmmUtils.serviceEndpoints(
      this.serviceUrl,
      this.formGroup.getRawValue(),
      this.formGroup.getRawValue().multiSelectableParam,
    );

    this.nshmpService
      .callServices$<GmmDistanceResponse>(urls)
      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
      .subscribe(serviceResponses => {
        this.handleServiceResponses(serviceResponses);
        spinnerRef.close();
      });
  }

  /**
   * Create plots based on current state and form group.
   */
  createPlots(): void {
    this.updateState({
      plots: this.serviceResponseToPlotData(this.state(), this.formGroup),
    });
  }

  /**
   * Default values for control panel.
   */
  defaultFormValues(): FormControls {
    return {
      dip: null,
      gmmGroupType: GmmGroupType.ACTIVE_CRUST,
      gmmSource: [],
      imt: 'default',
      multiSelectableParam: MultiSelectableParam.GMM,
      Mw: null,
      MwMulti: [],
      rMax: 300,
      rMin: 0.1,
      showEpistemicUncertainty: false,
      vs30: null,
      vs30Multi: [],
      width: null,
      z1p0: null,
      z2p5: null,
      zSed: null,
      zTor: null,
    };
  }

  /**
   * Returns the default plots.
   */
  defaultPlots(): Map<string, NshmpPlot> {
    const plots = new Map<string, NshmpPlot>();

    const meansPlotData = plotUtils.defaultPlot({
      id: gmmUtils.PlotType.MEANS,
      options: {
        layout: {
          aspectRatio: gmmUtils.MEAN_ASPECT_RATIO,
        },
      },
      title: 'Ground Motion vs. Distance',
      xLabel: 'Distance (km)',
      yLabel: 'Median ground motion (g)',
    });

    const meansPlotSettingsForm: NshmpPlotSettings = {
      config: meansPlotData.config,
      layout: plotUtils.plotlyLayoutToSettings(meansPlotData.layout),
    };
    plots.set(gmmUtils.PlotType.MEANS, {
      label: 'Means',
      plotData: meansPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup(meansPlotSettingsForm),
    });

    return new Map(plots);
  }

  /**
   * Initialize the application.
   */
  init(): void {
    const spinnerRef = this.spinnerService.show(
      SpinnerService.MESSAGE_METADATA,
    );

    this.nshmpService
      .callService$<GmmDistanceUsage>(this.serviceUrl)
      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
      .subscribe(usageResponse => {
        this.handleUsageResponse(usageResponse);
        this.initialFormSet();
        spinnerRef.close();
      });
  }

  /**
   * GMM distance app inital state
   */
  initialState(): AppState {
    return {
      plots: this.defaultPlots(),
      serviceCallInfo: {
        serviceCalls: [],
        serviceName: 'Ground Motion vs. Distance',
        usage: [],
      },
      serviceResponses: [],
      supportedImts: [],
      usageResponse: null,
    };
  }

  /**
   * Reset the control panel.
   */
  resetControlPanel(): void {
    this.formGroup.reset(
      this.usageFormValues(this.state().usageResponse.response.parameters),
    );
  }

  /**
   * Reset the plot settings.
   */
  resetPlotSettings(): void {
    super.resetPlotSettings({
      currentPlots: this.state().plots,
      defaultPlots: this.defaultPlots(),
    });
  }

  /**
   * Reset the state.
   */
  resetState(): void {
    const serviceCallInfo = gmmUtils.serviceCallInfo({
      multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
      serviceName: this.state().serviceCallInfo.serviceName,
      serviceResponses: this.state().serviceResponses,
      serviceUrl: this.serviceUrl,
      values: this.formGroup.getRawValue(),
    });

    this.updateState({
      serviceCallInfo,
      serviceResponses: null,
    });

    this.createPlots();
  }

  /**
   * Update state.
   *
   * @param state Partial new state to update
   */
  updateState(state: Partial<AppState>): void {
    this.state.set({
      ...this.state(),
      ...state,
    });
  }

  /**
   * Returns the default from values from parameters.
   *
   * @param parameters The service parameters
   */
  usageFormValues(parameters: GmmDistanceUsageParameters): FormControls {
    return {
      ...this.defaultFormValues(),
      dip: parameters.dip.value as number,
      Mw: parameters.Mw.value as number,
      vs30: parameters.vs30.value as number,
      width: parameters.width.value as number,
      z1p0: parameters.z1p0.value as number,
      z2p5: parameters.z2p5.value as number,
      zTor: parameters.zTor.value as number,
    };
  }

  private handleServiceResponses(
    serviceResponses: GmmDistanceResponse[],
  ): void {
    const means = serviceResponses.map(s => s.response.means);
    const sigmas = serviceResponses.map(s => s.response.sigmas);
    const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);

    if (hasLogicTree) {
      this.formGroup.controls.showEpistemicUncertainty.enable();
    } else {
      this.formGroup.controls.showEpistemicUncertainty.disable();
    }

    this.updateState({
      serviceCallInfo: gmmUtils.serviceCallInfo({
        multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
        serviceName: this.state().serviceCallInfo.serviceName,
        serviceResponses,
        serviceUrl: this.serviceUrl,
        values: this.formGroup.getRawValue(),
      }),
      serviceResponses,
    });

    this.createPlots();
  }

  private handleUsageResponse(usageResponse: GmmDistanceUsage): void {
    const parameters = usageResponse.response.parameters;
    const values = this.usageFormValues(parameters);

    this.formGroup.patchValue({
      ...values,
    });

    const serviceCallInfo: ServiceCallInfo = {
      ...this.state().serviceCallInfo,
      usage: [this.serviceUrl],
    };

    this.updateState({
      serviceCallInfo,
      usageResponse,
    });
  }

  private initialFormSet(): void {
    const query = this.route.snapshot.queryParams as Query;

    const defaultValues = this.formGroup.getRawValue();
    const parameters = this.usage().response.parameters;
    const gmmSource = gmmUtils.queryToGmmSource(query.gmm, parameters);

    this.formGroup.patchValue({
      multiSelectableParam:
        query?.multiSelectableParam ?? defaultValues.multiSelectableParam,
    });

    this.formGroup.patchValue({
      dip: nshmpUtils.queryParseNumber(defaultValues.dip, query?.dip),
      gmmGroupType: query?.gmmGroupType ?? defaultValues.gmmGroupType,
      gmmSource,
      imt: query?.imt ?? defaultValues.imt,
      Mw: nshmpUtils.queryParseNumber(defaultValues.Mw, query?.Mw),
      MwMulti: nshmpUtils
        .queryStringToArray(query?.MwMulti)
        .map(mw => Number.parseFloat(mw)),
      rMax: nshmpUtils.queryParseNumber(defaultValues.rMax, query?.rMax),
      rMin: nshmpUtils.queryParseNumber(defaultValues.rMin, query?.rMin),
      showEpistemicUncertainty: nshmpUtils.queryParseBoolean(
        defaultValues.showEpistemicUncertainty,
        query?.showEpistemicUncertainty,
      ),
      vs30: nshmpUtils.queryParseNumber(defaultValues.vs30, query?.vs30),
      vs30Multi: nshmpUtils
        .queryStringToArray(query?.vs30Multi)
        .map(vs30 => Number.parseFloat(vs30)),
      width: nshmpUtils.queryParseNumber(defaultValues.width, query?.width),
      z1p0: nshmpUtils.queryParseNumber(defaultValues.z1p0, query?.z1p0),
      z2p5: nshmpUtils.queryParseNumber(defaultValues.z2p5, query?.z2p5),
      zSed: nshmpUtils.queryParseNumber(defaultValues.zSed, query?.zSed),
      zTor: nshmpUtils.queryParseNumber(defaultValues.zTor, query?.zTor),
    });

    if (
      this.formGroup.value.multiSelectableParam === MultiSelectableParam.MW ||
      this.formGroup.value.multiSelectableParam === MultiSelectableParam.VS30
    ) {
      this.formGroup.patchValue({
        gmmSource: gmmSource.length > 0 ? [gmmSource.pop()] : [],
      });
    }

    if (this.formGroup.valid) {
      this.callService();
    } else {
      this.formGroup.markAsDirty();
    }

    this.formGroup.valueChanges.subscribe(() => this.updateUrl());
  }

  /**
   * Transform Gmm distance service responses to plots.
   *
   * @param state The application state
   */
  private serviceResponseToPlotData(
    state: AppState,
    form: FormGroupControls<FormControls>,
  ): Map<string, NshmpPlot> {
    if (
      state.serviceResponses === null ||
      state.serviceResponses?.length === 0
    ) {
      return this.defaultPlots();
    }

    const plots = new Map<string, NshmpPlot>();
    const formValues = form.getRawValue();
    const meanPlot = state.plots.get(gmmUtils.PlotType.MEANS);
    const hoverTemplate = '%{x} km, %{y} AFE';
    const title = `Ground Motion vs. Distance: ${imtToString(
      formValues.imt as Imt,
    )}`;

    meanPlot.settingsForm.patchValue({
      layout: {
        title: {
          text: title,
        },
      },
    });

    const responses: gmmUtils.GmmResponse<XySequence, number[]>[] =
      state.serviceResponses.map(serviceResponse => ({
        input: serviceResponse.request.input,
        meanPlotConfig: {
          hoverTemplate,
        },
        response: serviceResponse.response,
        sigmaPlotConfig: {
          hoverTemplate,
        },
      }));

    const gmmPlots = gmmUtils.gmmResponsesToPlots({
      meanPlot,
      meanTitle: title,
      multiSelectableParam: formValues.multiSelectableParam,
      responses,
      showEpistemicUncertainty: formValues.showEpistemicUncertainty,
      sigmaPlot: meanPlot,
    });

    plots.set(gmmUtils.PlotType.MEANS, {
      ...meanPlot,
      plotData: gmmPlots.means,
    });

    return plots;
  }

  private updateUrl(): void {
    const value = this.formGroup.getRawValue();

    const query: Query = {
      dip: value.dip?.toString(),
      gmm: gmmUtils.gmmSourceToQuery(value.gmmSource),
      gmmGroupType: value.gmmGroupType,
      imt: value.imt,
      multiSelectableParam: value.multiSelectableParam,
      Mw: value.Mw?.toString(),
      MwMulti: value.MwMulti?.map(mw => mw.toString()),
      rMax: value.rMax?.toString(),
      rMin: value.rMin?.toString(),
      showEpistemicUncertainty: value.showEpistemicUncertainty.toString(),
      vs30: value.vs30?.toString(),
      vs30Multi: value.vs30Multi?.map(vs30 => vs30.toString()),
      width: value.width?.toString(),
      z1p0: value.z1p0?.toString(),
      z2p5: value.z2p5?.toString(),
      zSed: value.zSed?.toString(),
      zTor: value.zTor?.toString(),
    };

    this.location.replaceState(
      apps().gmm.distance.routerLink,
      new HttpParams().appendAll({...query}).toString(),
    );
  }
}

results matching ""

    No results matching ""