File

services/app.service.ts

Description

Entrypoint to store for dynamic hazard compare application.

Extends

SharedService

Index

Properties
Methods
Accessors

Constructor

constructor(formBuilder: FormBuilder, spinnerService: SpinnerService, nshmpService: NshmpService, hazardPlotsService: HazardPlotsService, spectraPlotsService: SpectraPlotsService, hazardService: HazardService, route: ActivatedRoute, location: LocationService)
Parameters :
Name Type Optional
formBuilder FormBuilder No
spinnerService SpinnerService No
nshmpService NshmpService No
hazardPlotsService HazardPlotsService No
spectraPlotsService SpectraPlotsService No
hazardService HazardService No
route ActivatedRoute No
location LocationService No

Methods

Private addRequiredValidator
addRequiredValidator(control: AbstractControl)
Parameters :
Name Type Optional
control AbstractControl No
Returns : void
addValidators
addValidators()
Returns : void
callService
callService()

Call the hazard service.

Returns : void
combineUsages
combineUsages(usages: HazardUsageResponse[])

Combine hazard usages with same region with common parameters.

Parameters :
Name Type Optional Description
usages HazardUsageResponse[] No

The usages with same region

Returns : HazardUsageResponse
createPlots
createPlots()
Returns : void
defaultFormValues
defaultFormValues()

Initial state for the control panel,

Returns : ControlForm
defaultPlots
defaultPlots()

Returns the default plots with settings.

Returns : Map<string, NshmpPlot>
Private findCommonImts
findCommonImts(imts: Parameter[][])

Returns the common imts form array of parameters.

Parameters :
Name Type Optional Description
imts Parameter[][] No

The imt parameters

Returns : Parameter[]
Private findCommonSiteClasses
findCommonSiteClasses(siteClasses: Record[])

Returns the common site classes based on keys from an array of site class records.

Parameters :
Name Type Optional Description
siteClasses Record<string, number>[] No

The site classes

Returns : Record<string, number>
Private getCombinedUsage
getCombinedUsage(usageResponses: Map, model: NshmId, comparableModel: string | null)

Returns the combined usage from model and comparable model.

Parameters :
Name Type Optional Description
usageResponses Map<string | HazardUsageResponse> No

The usages

model NshmId No

The current selected model

comparableModel string | null No

The current comparable model

Returns : HazardUsageResponse
Private getComparableModels
getComparableModels()

Returns the available models that are comparable to the select model.

Returns : Parameter[]
Private getImt
getImt(query: string, defaultImt: Imt)

Returns the IMT to use.

Parameters :
Name Type Optional Description
query string No

The query value

defaultImt Imt No

The default IMT

Returns : Imt
Private getModel
getModel(query: string, defaultModel: NshmId)

Returns the model to use.

Parameters :
Name Type Optional Description
query string No

The query model

defaultModel NshmId No

The default model

Returns : NshmId
Private getSiteClass
getSiteClass(query: string, defaultSiteClass: NehrpSiteClass)

Returns the site class to use.

Parameters :
Name Type Optional Description
query string No

The query value

defaultSiteClass NehrpSiteClass No

The default site class

Returns : NehrpSiteClass
init
init()

Initialize applicaiton.

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

Dynamic hazard compare initial state.

Returns : AppState
onModelChange
onModelChange()

Returns the updated state for a model change.

Returns : void
resetControlPanel
resetControlPanel()

Reset the control panel.

Returns : void
resetSettings
resetSettings()

Reset the plot settings.

Returns : void
resetState
resetState()
Returns : void
Private responseSpectra
responseSpectra(hazardResponses: HazardResponse[], form: ControlForm)

Create the response spectra for a specific model and for each source type available.

Parameters :
Name Type Optional Description
hazardResponses HazardResponse[] No

The hazard responses

form ControlForm No

The control form values

Returns : Spectra[]
Private responseToPlots
responseToPlots(state: AppState, formGroup: FormGroupControls<ControlForm>)

Create the plots.

Parameters :
Name Type Optional Description
state AppState No

The app state

formGroup FormGroupControls<ControlForm> No
Returns : Map<string, NshmpPlot>
Private serviceCallUrl
serviceCallUrl(model: NshmId, formValues: ControlForm, nshmServices: NshmMetadata[])

Returns the URL to call for a specific model.

Parameters :
Name Type Optional Description
model NshmId No

The NSHM to call

formValues ControlForm No

The control panel form values

nshmServices NshmMetadata[] No

The NSHM service metadata

Returns : string
serviceCallUrls
serviceCallUrls(formValues: ControlForm, nshmServices: NshmMetadata[])

Returns the URLs to call.

Parameters :
Name Type Optional Description
formValues ControlForm No

The control panel form values

nshmServices NshmMetadata[] No

The NSHM service metadata

Returns : string[]
Private setHazardPlots
setHazardPlots(plots: Map)

Creates and sets the default hazard plots.

Parameters :
Name Type Optional Description
plots Map<string | NshmpPlot> No

The map of the plots to set

Returns : void
setLocation
setLocation(location: Location)

Set the location form fields.

Parameters :
Name Type Optional Description
location Location No

The location

Returns : void
Private setSpectraPlots
setSpectraPlots(plots: Map)

Creates and sets the default spectra plots.

Parameters :
Name Type Optional Description
plots Map<string | NshmpPlot> No

The map of the plots to set

Returns : void
updateState
updateState(state: Partial<AppState>)
Parameters :
Name Type Optional
state Partial<AppState> No
Returns : void
Private updateUrl
updateUrl()
Returns : void

Properties

Readonly formGroup
Default value : this.formBuilder.group<ControlForm>( this.defaultFormValues(), )
Private nshmpHazWs
Default value : environment.webServices.nshmpHazWs

nshmp-haz-ws web config

Private serviceEndpoint
Default value : this.nshmpHazWs.services.curveServices.hazard

Hazard endpoint

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

Accessors

availableModels
getavailableModels()

Returns the available dynamic models.

Returns : Signal<Parameter[]>
comparableModels
getcomparableModels()

Returns the models that can been compared with selected model.

Returns : Signal<Parameter[]>
nshmService
getnshmService()

Returns the metadata of the NSHM observable.

Returns : Signal<NshmMetadata>
plots
getplots()

Returns the Map of the plots.

Returns : Signal<Map<string, NshmpPlot>>
serviceCallInfo
getserviceCallInfo()

Returns the service call info.

Returns : Signal<ServiceCallInfo>
serviceResponses
getserviceResponses()

Returns the service responses.

Returns : Signal<ServiceResponses>
usage
getusage()

Return the usage for the selected model.

Returns : Signal<HazardUsageResponse>
usageModelA
getusageModelA()

Return the usage for the selected model.

Returns : Signal<HazardUsageResponse>
usageModelB
getusageModelB()

Return the usage for the selected model.

Returns : Signal<HazardUsageResponse>
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 {HazardService, hazardUtils} from '@ghsc/nshmp-lib-ng/hazard';
import {
  FormGroupControls,
  NshmpService,
  ServiceCallInfo,
  SpinnerService,
} from '@ghsc/nshmp-lib-ng/nshmp';
import {NshmpPlot, PlotOptions, plotUtils} from '@ghsc/nshmp-lib-ng/plot';
import {
  HazardCalcResponse,
  HazardRequestMetadata,
  HazardResponse,
  HazardUsageResponse,
} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service';
import {NshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/nshm-service';
import {Location} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/geo';
import {Imt, NehrpSiteClass} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm';
import {
  SourceType,
  sourceTypeFromPascalCase,
  sourceTypeToCapitalCase,
} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model';
import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm';
import {Parameter} 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 {devApps} from 'projects/nshmp-apps/src/shared/utils/applications.utils';
import {catchError, forkJoin, Observable} from 'rxjs';

import {
  AppState,
  ControlForm,
  Plots,
  ServiceResponses,
  Spectra,
} from '../models/state.model';
import {HazardPlotsService} from './hazard-plots.service';
import {SpectraPlotsService} from './spectra-plots.service';

/**
 * URL query.
 */
interface Query {
  imt: string;
  latitude: string;
  longitude: string;
  maxDirection: string;
  model: string;
  modelCompare: string;
  returnPeriod: string;
  siteClass: string;
  truncate: string;
  vs30: string;
}

/**
 * Entrypoint to store for dynamic hazard compare application.
 */
@Injectable({
  providedIn: 'root',
})
export class AppService
  extends SharedService
  implements AppServiceModel<AppState, ControlForm>
{
  /** nshmp-haz-ws web config */
  private nshmpHazWs = environment.webServices.nshmpHazWs;
  /** Hazard endpoint */
  private serviceEndpoint = this.nshmpHazWs.services.curveServices.hazard;

  readonly formGroup = this.formBuilder.group<ControlForm>(
    this.defaultFormValues(),
  );
  readonly state = signal<AppState>(this.initialState());

  constructor(
    private formBuilder: FormBuilder,
    private spinnerService: SpinnerService,
    private nshmpService: NshmpService,
    private hazardPlotsService: HazardPlotsService,
    private spectraPlotsService: SpectraPlotsService,
    private hazardService: HazardService,
    private route: ActivatedRoute,
    private location: LocationService,
  ) {
    super();
    this.addValidators();
  }

  /**
   * Returns the available dynamic models.
   */
  get availableModels(): Signal<Parameter[]> {
    return computed(() => this.state().availableModels);
  }

  /**
   * Returns the models that can been compared with selected model.
   */
  get comparableModels(): Signal<Parameter[]> {
    return computed(() => this.state().comparableModels);
  }

  /**
   * Returns the metadata of the NSHM observable.
   */
  get nshmService(): Signal<NshmMetadata> {
    return computed(() =>
      this.state().nshmServices.find(
        nshmService => nshmService.model === this.formGroup.getRawValue().model,
      ),
    );
  }

  /**
   * Returns the `Map` of the plots.
   */
  get plots(): Signal<Map<string, NshmpPlot>> {
    return computed(() => this.state().plots);
  }

  /**
   * Returns the service call info.
   */
  get serviceCallInfo(): Signal<ServiceCallInfo> {
    return computed(() => this.state().serviceCallInfo);
  }

  /**
   * Returns the service responses.
   */
  get serviceResponses(): Signal<ServiceResponses> {
    return computed(() => this.state().serviceResponses);
  }

  /**
   * Return the usage for the selected model.
   */
  get usage(): Signal<HazardUsageResponse> {
    return computed(() => this.state().combinedUsage);
  }

  /**
   * Return the usage for the selected model.
   */
  get usageModelA(): Signal<HazardUsageResponse> {
    return computed(() =>
      this.state().usageResponses?.get(this.formGroup.getRawValue().model),
    );
  }

  /**
   * Return the usage for the selected model.
   */
  get usageModelB(): Signal<HazardUsageResponse> {
    return computed(() =>
      this.state().usageResponses?.get(
        this.formGroup.getRawValue().modelCompare,
      ),
    );
  }

  addValidators(): void {
    const controls = this.formGroup.controls;

    this.addRequiredValidator(controls.imt);
    this.addRequiredValidator(controls.latitude);
    this.addRequiredValidator(controls.longitude);
    this.addRequiredValidator(controls.model);
    this.addRequiredValidator(controls.modelCompare);
    this.addRequiredValidator(controls.vs30);
    this.addRequiredValidator(controls.returnPeriod);

    controls.latitude.addValidators(this.validateNan());
    controls.longitude.addValidators(this.validateNan());
  }

  /**
   * Call the hazard service.
   */
  callService(): void {
    const spinnerRef = this.spinnerService.show(
      `${SpinnerService.MESSAGE_SERVICE}
      <br>
      (Could take 30+ seconds)
      `,
    );
    const form = this.formGroup.getRawValue();

    const modelUrl = this.serviceCallUrl(
      form.model,
      form,
      this.state().nshmServices,
    );

    const modelCompareUrl = this.serviceCallUrl(
      form.modelCompare,
      form,
      this.state().nshmServices,
    );

    const serviceCallInfo: ServiceCallInfo = {
      ...this.state().serviceCallInfo,
      serviceCalls: [modelUrl, modelCompareUrl],
    };

    const calls: Observable<HazardCalcResponse>[] = [
      modelUrl,
      modelCompareUrl,
    ].map(url => this.nshmpService.callService$(url));

    forkJoin(calls)
      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
      .subscribe(hazardResponses => {
        if (hazardResponses.length !== 2) {
          throw new Error('Does not contain two responses');
        }

        const modelA = hazardResponses[0];
        const modelB = hazardResponses[1];

        const serviceResponses: ServiceResponses = {
          modelA: {
            hazardResponse: modelA,
            model: form.model,
            spectra: this.responseSpectra(modelA.response.hazardCurves, form),
          },
          modelB: {
            hazardResponse: modelB,
            model: form.modelCompare,
            spectra: this.responseSpectra(modelB.response.hazardCurves, form),
          },
        };

        this.updateState({serviceCallInfo, serviceResponses});
        this.createPlots();
        spinnerRef.close();
      });
  }

  /**
   * Combine hazard usages with same region with common parameters.
   *
   * @param usages The usages with same region
   */
  combineUsages(usages: HazardUsageResponse[]): HazardUsageResponse {
    const gmms = usages.map(usage => usage.response.model.gmms);
    const imts = usages.map(usage => usage.response.model.imts);
    const siteClasses = this.findCommonSiteClasses(
      usages.map(usage => usage.response.model.siteClasses),
    );
    const usage = [...usages].shift();

    const combinedUsage: HazardUsageResponse = {
      ...usage,
      response: {
        latitude: {
          ...usage.response.latitude,
          max: Math.min(...usages.map(usage => usage.response.latitude.max)),
          min: Math.max(...usages.map(usage => usage.response.latitude.min)),
        },
        longitude: {
          ...usage.response.longitude,
          max: Math.min(...usages.map(usage => usage.response.longitude.max)),
          min: Math.max(...usages.map(usage => usage.response.longitude.min)),
        },
        model: {
          bounds: usage.response.model.bounds,
          gmms: gmms.reduce((previous, current) =>
            previous.filter(gmm => current.includes(gmm)),
          ),
          imts: this.findCommonImts(imts),
          name: 'Combined usage',
          siteClasses,
        },
        vs30: {
          ...usage.response.vs30,
          max: Math.min(...usages.map(usage => usage.response.vs30.max)),
          min: Math.max(...usages.map(usage => usage.response.vs30.min)),
        },
      },
    };

    return combinedUsage;
  }

  createPlots(): void {
    const plots = this.responseToPlots(this.state(), this.formGroup);
    this.updateState({plots});
  }

  /**
   * Initial state for the control panel,
   */
  defaultFormValues(): ControlForm {
    return {
      ...hazardUtils.hazardDefaultFormValues(),
      imt: Imt.PGA,
      modelCompare: null,
      sourceType: sourceTypeToCapitalCase(SourceType.TOTAL),
      vs30: 760,
    };
  }

  /**
   * Returns the default plots with settings.
   */
  defaultPlots(): Map<string, NshmpPlot> {
    const plots = new Map<string, NshmpPlot>();
    this.setHazardPlots(plots);
    this.setSpectraPlots(plots);
    return plots;
  }

  /**
   * Initialize applicaiton.
   */
  init(): void {
    this.spinnerService.show(SpinnerService.MESSAGE_METADATA);

    this.hazardService
      .dynamicNshms$<HazardRequestMetadata>(
        `${this.nshmpHazWs.url}${this.nshmpHazWs.services.nshms}`,
        this.serviceEndpoint,
      )
      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
      .subscribe(({models, nshmServices, usageResponses}) => {
        this.spinnerService.remove();
        const serviceCallInfo: ServiceCallInfo = {
          ...this.state().serviceCallInfo,
          usage: nshmServices.map(
            service => `${service.url}${this.serviceEndpoint}`,
          ),
        };

        this.updateState({
          availableModels: models,
          nshmServices,
          serviceCallInfo,
          usageResponses,
        });

        this.onModelChange();
        this.initialFormSet();
      });
  }

  /**
   * Dynamic hazard compare initial state.
   */
  initialState(): AppState {
    return {
      availableModels: [],
      combinedUsage: null,
      comparableModels: [],
      nshmServices: [],
      plots: this.defaultPlots(),
      serviceCallInfo: {
        serviceCalls: [],
        serviceName: 'Dynamic Hazard Curves',
        usage: [],
      },
      serviceResponses: {
        modelA: null,
        modelB: null,
      },
      usageResponses: null,
    };
  }

  /**
   * Returns the updated state for a model change.
   *
   * @param state The current application state
   */
  onModelChange(): void {
    const comparableModels = this.getComparableModels();
    const defaultComparableModel = [...comparableModels]?.pop()?.value ?? null;
    const combinedUsage = this.getCombinedUsage(
      this.state().usageResponses,
      this.formGroup.getRawValue().model,
      defaultComparableModel,
    );

    if (
      this.formGroup.getRawValue().modelCompare !==
      (defaultComparableModel as NshmId)
    ) {
      this.formGroup.patchValue({
        modelCompare: defaultComparableModel as NshmId,
      });
    }

    this.updateState({
      combinedUsage,
      comparableModels,
      plots: this.initialState().plots,
      serviceCallInfo: {
        ...this.state().serviceCallInfo,
        serviceCalls: [],
      },
      serviceResponses: this.initialState().serviceResponses,
    });
  }

  /**
   * Reset the control panel.
   */
  resetControlPanel(): void {
    this.formGroup.reset(this.defaultFormValues());
    this.resetState();
    this.onModelChange();
  }

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

  resetState(): void {
    this.updateState({
      plots: this.initialState().plots,
      serviceCallInfo: {
        ...this.state().serviceCallInfo,
        serviceCalls: [],
      },
    });
  }

  /**
   * Returns the URLs to call.
   *
   * @param formValues The control panel form values
   * @param nshmServices The NSHM service metadata
   * @param serviceEndpoint The service endpoint to call
   */
  serviceCallUrls(
    formValues: ControlForm,
    nshmServices: NshmMetadata[],
  ): string[] {
    const {imt, latitude, longitude, model, modelCompare, vs30} = formValues;

    const services = [model, modelCompare].map(model =>
      nshmServices.find(service => service.model === model),
    );

    const urls = services.map(
      nshmService =>
        `${nshmService.url}${this.serviceEndpoint}/${longitude}/${latitude}/${vs30}?imt=${imt}`,
    );

    return urls;
  }

  /**
   * Set the location form fields.
   *
   * @param location The location
   */
  setLocation(location: Location): void {
    this.formGroup.patchValue({
      latitude: location.latitude,
      longitude: location.longitude,
    });
  }

  updateState(state: Partial<AppState>): void {
    this.state.set({
      ...this.state(),
      ...state,
    });
  }

  private addRequiredValidator(control: AbstractControl): void {
    control.addValidators(control => Validators.required(control));
  }

  /**
   * Returns the common imts form array of parameters.
   *
   * @param imts The imt parameters
   */
  private findCommonImts(imts: Parameter[][]): Parameter[] {
    return imts.reduce((previous, current) =>
      previous.filter(previousParameter =>
        current
          .map(currentParameter => currentParameter.value)
          .includes(previousParameter.value),
      ),
    );
  }

  /**
   * Returns the common site classes based on keys from an array of site class
   * records.
   *
   * @param siteClasses The site classes
   */
  private findCommonSiteClasses(
    siteClasses: Record<string, number>[],
  ): Record<string, number> {
    const allSiteClasses = siteClasses.reduce((previous, current) => ({
      ...previous,
      ...current,
    }));

    const startingKeys = Object.keys(siteClasses.shift());

    const keys = siteClasses.reduce((commonKeys, item) => {
      return commonKeys.filter(key => Object.keys(item).includes(key));
    }, startingKeys);

    const commonSiteClasses: Record<string, number> = {};
    keys.forEach(key => (commonSiteClasses[key] = allSiteClasses[key]));

    return commonSiteClasses;
  }

  /**
   * Returns the combined usage from model and comparable model.
   *
   * @param usageResponses The usages
   * @param model The current selected model
   * @param comparableModel The current comparable model
   */
  private getCombinedUsage(
    usageResponses: Map<string, HazardUsageResponse>,
    model: NshmId,
    comparableModel: string | null,
  ): HazardUsageResponse {
    let combinedUsage = usageResponses.get(model);

    if (comparableModel !== null) {
      combinedUsage = this.combineUsages([
        usageResponses.get(model),
        usageResponses.get(comparableModel),
      ]);
    }

    return combinedUsage;
  }

  /**
   * Returns the available models that are comparable to the select model.
   *
   * @param state The application state
   */
  private getComparableModels(): Parameter[] {
    const formValues = this.formGroup.getRawValue();
    const state = this.state();

    const currentNshmService = state.nshmServices.find(
      nshmService => nshmService.model === formValues.model,
    );

    const comparableServices = state.nshmServices.filter(
      nshmService =>
        nshmService.model !== currentNshmService.model &&
        nshmService.project === currentNshmService.project,
    );

    return state.availableModels.filter(model =>
      comparableServices.find(
        service => service.model.toString() === model.value,
      ),
    );
  }

  /**
   * Returns the IMT to use.
   *
   * @param query The query value
   * @param defaultImt The default IMT
   */
  private getImt(query: string, defaultImt: Imt): Imt {
    return (
      Object.values(Imt).find(imt => imt.toString() === query) ?? defaultImt
    );
  }

  /**
   * Returns the model to use.
   *
   * @param query The query model
   * @param defaultModel The default model
   */
  private getModel(query: string, defaultModel: NshmId): NshmId {
    return (
      Object.values(NshmId).find(model => model.toString() === query) ??
      defaultModel
    );
  }

  /**
   * Returns the site class to use.
   *
   * @param query The query value
   * @param defaultSiteClass The default site class
   */
  private getSiteClass(
    query: string,
    defaultSiteClass: NehrpSiteClass,
  ): NehrpSiteClass {
    return (
      Object.values(NehrpSiteClass).find(
        siteClass => siteClass.toString() === query,
      ) ?? defaultSiteClass
    );
  }

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

    const formValues: ControlForm = {
      commonReturnPeriods: defaultValues.commonReturnPeriods,
      imt: this.getImt(query.imt, defaultValues.imt),
      latitude: defaultValues.latitude,
      longitude: defaultValues.longitude,
      maxDirection:
        query.maxDirection !== undefined
          ? (JSON.parse(query.maxDirection) as boolean)
          : defaultValues.maxDirection,
      model: this.getModel(query.model, defaultValues.model),
      modelCompare: this.getModel(
        query.modelCompare,
        defaultValues.modelCompare,
      ),
      returnPeriod: query.returnPeriod
        ? Number.parseInt(query.returnPeriod, 10)
        : defaultValues.returnPeriod,
      siteClass: this.getSiteClass(
        query.siteClass,
        defaultValues.siteClass as NehrpSiteClass,
      ),
      sourceType: null,
      truncate:
        query.truncate !== undefined
          ? (JSON.parse(query.truncate) as boolean)
          : defaultValues.truncate,
      vs30: query.vs30 ? Number.parseFloat(query.vs30) : defaultValues.vs30,
    };

    this.formGroup.patchValue(formValues);
    this.formGroup.patchValue({
      latitude: query.latitude
        ? Number.parseFloat(query.latitude)
        : defaultValues.latitude,
      longitude: query.longitude
        ? Number.parseFloat(query.longitude)
        : defaultValues.longitude,
    });

    if (this.formGroup.valid) {
      this.nshmpService.selectPlotControl();
      this.callService();
    } else if (this.formGroup.getRawValue() !== defaultValues) {
      this.formGroup.markAsDirty();
    }

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

  /**
   * Create the response spectra for a specific model and for each source type available.
   *
   * @param hazardResponses The hazard responses
   * @param form The control form values
   */
  private responseSpectra(
    hazardResponses: HazardResponse[],
    form: ControlForm,
  ): Spectra[] {
    const spectra: Spectra[] = [];
    const sourceTypes = [...hazardResponses]
      .shift()
      .data.map(data => data.component);

    sourceTypes.forEach(sourceType => {
      form.sourceType = sourceType;
      spectra.push({
        responseSpectra: hazardUtils.responseSpectra(hazardResponses, form),
        sourceType: sourceTypeFromPascalCase(sourceType),
      });
    });

    return spectra;
  }

  /**
   * Create the plots.
   *
   * @param state The app state
   */
  private responseToPlots(
    state: AppState,
    formGroup: FormGroupControls<ControlForm>,
  ): Map<string, NshmpPlot> {
    if (
      state.serviceResponses.modelA === null ||
      state.serviceResponses.modelB === null
    ) {
      return state.plots;
    }

    const hazardPlots = this.hazardPlotsService.createHazardPlots(
      state,
      formGroup,
    );
    const spectraPlots = this.spectraPlotsService.createSpectraPlots(
      state,
      formGroup,
    );
    return new Map([...hazardPlots.entries(), ...spectraPlots.entries()]);
  }

  /**
   * Returns the URL to call for a specific model.
   *
   * @param model The NSHM to call
   * @param formValues The control panel form values
   * @param nshmServices The NSHM service metadata
   */
  private serviceCallUrl(
    model: NshmId,
    formValues: ControlForm,
    nshmServices: NshmMetadata[],
  ): string {
    const {latitude, longitude, vs30} = formValues;
    const service = nshmServices.find(service => service.model === model);
    return `${service.url}${this.serviceEndpoint}/${longitude}/${latitude}/${vs30}`;
  }

  /**
   * Creates and sets the default hazard plots.
   *
   * @param plots The map of the plots to set
   */
  private setHazardPlots(plots: Map<string, NshmpPlot>): void {
    const hazardXLabel = 'Ground Motion (g)';
    const hazardYLabel = 'Annual Frequency of Exceedence';

    const hazardPlotData = plotUtils.defaultPlot({
      id: 'hazard-curves',
      mobileOptions: plotOptions,
      options: plotOptions,
      title: 'Hazard Curves',
      xLabel: hazardXLabel,
      yLabel: hazardYLabel,
    });

    plots.set(Plots.HAZARD, {
      label: 'Hazard Curves',
      plotData: hazardPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: hazardPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(hazardPlotData.layout),
      }),
    });

    const hazardDiffPlotData = plotUtils.defaultPlot({
      id: 'hazard-curves-differences',
      mobileOptions: diffPlotOptions,
      options: diffPlotOptions,
      title: 'Hazard Curves: % Difference',
      xLabel: 'Ground Motion (g)',
      yLabel: '% Difference',
    });

    plots.set(Plots.HAZARD_DIFFERENCES, {
      label: 'Hazard Curve Differences',
      plotData: hazardDiffPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: hazardDiffPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(hazardDiffPlotData.layout),
      }),
    });

    const hazardComponentsPlotData = plotUtils.defaultPlot({
      id: 'hazard-component-curves',
      mobileOptions: plotOptions,
      options: plotOptions,
      title: 'Hazard Components Curves',
      xLabel: hazardXLabel,
      yLabel: hazardYLabel,
    });

    plots.set(Plots.HAZARD_COMPONENTS, {
      label: 'Hazard Component Curves',
      plotData: hazardComponentsPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: hazardComponentsPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(
          hazardComponentsPlotData.layout,
        ),
      }),
    });
  }

  /**
   * Creates and sets the default spectra plots.
   *
   * @param plots The map of the plots to set
   */
  private setSpectraPlots(plots: Map<string, NshmpPlot>): void {
    const spectraXLabel = 'Spectral Period (s)';
    const spectraYLabel = 'Ground Motion (g)';

    const spectrumPlotData = plotUtils.defaultPlot({
      id: 'response-spectrum',
      mobileOptions: plotOptions,
      options: plotOptions,
      title: 'Uniform Hazard Response Spectra',
      xLabel: spectraXLabel,
      yLabel: spectraYLabel,
    });

    plots.set(Plots.SPECTRUM, {
      label: 'Spectrum',
      plotData: spectrumPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: spectrumPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(spectrumPlotData.layout),
      }),
    });

    const spectrumDiffPlotData = plotUtils.defaultPlot({
      id: 'spectrum-differences',
      mobileOptions: diffPlotOptions,
      options: diffPlotOptions,
      title: 'Spectrum: % Difference',
      xLabel: 'Spectral Period (s)',
      yLabel: '% Difference',
    });

    plots.set(Plots.SPECTRUM_DIFFERENCES, {
      label: 'Spectrum Differences',
      plotData: spectrumDiffPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: spectrumDiffPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(spectrumDiffPlotData.layout),
      }),
    });

    const spectraComponentsPlotData = plotUtils.defaultPlot({
      id: 'spectra-component-curves',
      mobileOptions: plotOptions,
      options: plotOptions,
      title: 'Spectra Components Curves',
      xLabel: spectraXLabel,
      yLabel: spectraYLabel,
    });

    plots.set(Plots.SPECTRUM_COMPONENTS, {
      label: 'Spectra Component Curves',
      plotData: spectraComponentsPlotData,
      settingsForm: plotUtils.plotSettingsToFormGroup({
        config: spectraComponentsPlotData.config,
        layout: plotUtils.plotlyLayoutToSettings(
          spectraComponentsPlotData.layout,
        ),
      }),
    });
  }

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

    const query: Query = {
      imt: value.imt,
      latitude: value.latitude.toFixed(),
      longitude: value.longitude.toFixed(),
      maxDirection: String(value.maxDirection),
      model: value.model,
      modelCompare: value.modelCompare,
      returnPeriod: value.returnPeriod?.toString(),
      siteClass: value.siteClass,
      truncate: String(value.truncate),
      vs30: value.vs30?.toString(),
    };

    this.location.replaceState(
      devApps().hazard.dynamicCompare.routerLink,
      new HttpParams().appendAll({...query}).toString(),
    );
  }
}

const plotOptions: PlotOptions = {
  layout: {
    aspectRatio: hazardUtils.HAZARD_ASPECT_RATIO,
  },
};
const diffPlotOptions: PlotOptions = {
  layout: {
    aspectRatio: '3:1',
    yaxis: {
      range: [-100, 100],
      type: 'linear',
    },
  },
};

results matching ""

    No results matching ""