File

services/app.service.ts

Description

Entrypoint to store for dynamic hazard compare application.

Extends

SharedService

Index

Properties
Methods
Accessors

Constructor

constructor()

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

Private destroyRef
Default value : inject(DestroyRef)
Private formBuilder
Default value : inject(FormBuilder)
Readonly formGroup
Default value : this.formBuilder.group<ControlForm>(this.defaultFormValues())
Private hazardPlotsService
Default value : inject(HazardPlotsService)
Private hazardService
Default value : inject(HazardService)
Private location
Default value : inject(LocationService)
Private nshmpHazWs
Default value : environment.webServices.nshmpHazWs

nshmp-haz-ws web config

Private nshmpService
Default value : inject(NshmpService)
Private route
Default value : inject(ActivatedRoute)
Private serviceEndpoint
Default value : this.nshmpHazWs.services.curveServices.hazard

Hazard endpoint

Private spectraPlotsService
Default value : inject(SpectraPlotsService)
Private spinnerService
Default value : inject(SpinnerService)
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, DestroyRef, inject, Injectable, Signal, signal} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
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} from '@ghsc/nshmp-lib-ng/nshmp';
import {NshmpPlot, PlotOptions, plotUtils} from '@ghsc/nshmp-lib-ng/plot';
import {SpinnerService} from '@ghsc/nshmp-template';
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 deepEqual from 'deep-equal';
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> {
  private formBuilder = inject(FormBuilder);
  private spinnerService = inject(SpinnerService);
  private nshmpService = inject(NshmpService);
  private hazardPlotsService = inject(HazardPlotsService);
  private spectraPlotsService = inject(SpectraPlotsService);
  private hazardService = inject(HazardService);
  private route = inject(ActivatedRoute);
  private location = inject(LocationService);
  private destroyRef = inject(DestroyRef);

  /** 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() {
    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(takeUntilDestroyed(this.destroyRef))
      .pipe(
        catchError((error: Error) => {
          spinnerRef.close();
          return 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 {
    const spinnerRef = this.spinnerService.show(SpinnerService.MESSAGE_METADATA);

    this.hazardService
      .dynamicNshms$<HazardRequestMetadata>(
        `${this.nshmpHazWs.url}${this.nshmpHazWs.services.nshms}`,
        this.serviceEndpoint,
      )
      .pipe(takeUntilDestroyed(this.destroyRef))
      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
      .subscribe(({models, nshmServices, usageResponses}) => {
        spinnerRef.close();
        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 {
    const updatedState = {
      ...this.state(),
      ...state,
    };

    if (!deepEqual(updatedState, this.state())) {
      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
      .pipe(takeUntilDestroyed(this.destroyRef))
      .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);
    form.truncate = false;

    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 ""