diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.html b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..ee6f0cfe129dad9627c8f7ef5394dbd1fd83dc6e
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.html
@@ -0,0 +1,15 @@
+@if (control) {
+  <!-- Building code -->
+  <mat-form-field class="grid-col-12 margin-bottom-3">
+    <mat-label>{{ parameters()?.building_code.label }}</mat-label>
+
+    <mat-select [formControl]="control">
+      @for (buildingCode of buildingCodes(); track buildingCode) {
+        <mat-option [value]="buildingCode">
+          {{ buildingCode }}
+        </mat-option>
+      }
+    </mat-select>
+    <mat-hint>{{ parameters()?.building_code.info }}</mat-hint>
+  </mat-form-field>
+}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.scss b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.spec.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..881b7520dcb4db67f0b74e7f7077a477314b1868
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.spec.ts
@@ -0,0 +1,30 @@
+import {provideHttpClient} from '@angular/common/http';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
+import {provideRouter} from '@angular/router';
+
+import {BuildingCodeControlComponent} from './building-code-control.component';
+
+describe('BuildingCodeControlComponent', () => {
+  let component: BuildingCodeControlComponent;
+  let fixture: ComponentFixture<BuildingCodeControlComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [BuildingCodeControlComponent],
+      providers: [
+        provideHttpClient(),
+        provideNoopAnimations(),
+        provideRouter([]),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(BuildingCodeControlComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..86b2d1e0c6fbd86eee34c995583b3a4ca20bd815
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/building-code-control/building-code-control.component.ts
@@ -0,0 +1,59 @@
+import {Component, computed, Input, OnDestroy, OnInit} from '@angular/core';
+import {FormControl, ReactiveFormsModule} from '@angular/forms';
+import {MatFormFieldModule} from '@angular/material/form-field';
+import {MatSelectModule} from '@angular/material/select';
+import {Subscription} from 'rxjs';
+
+import {AppService} from '../../services/app.service';
+
+@Component({
+  imports: [MatFormFieldModule, ReactiveFormsModule, MatSelectModule],
+  selector: 'app-building-code-control',
+  standalone: true,
+  styleUrl: './building-code-control.component.scss',
+  templateUrl: './building-code-control.component.html',
+})
+export class BuildingCodeControlComponent implements OnInit, OnDestroy {
+  @Input({required: true})
+  control: FormControl<string>;
+
+  buildingCodes = computed(
+    () => this.service.usage()?.response.parameters.building_code.values
+  );
+
+  parameters = computed(() => this.service.usage()?.response.parameters);
+
+  private subs: Subscription[] = [];
+
+  constructor(public service: AppService) {}
+
+  ngOnInit(): void {
+    this.subs.push(
+      this.service.formGroup.controls.buildingCode.valueChanges.subscribe(
+        buildingCode => {
+          const values = this.service.hazardFormGroup.getRawValue();
+
+          if (values.buildingCode !== buildingCode) {
+            this.service.hazardFormGroup.patchValue({buildingCode});
+          }
+        }
+      )
+    );
+
+    this.subs.push(
+      this.service.hazardFormGroup.controls.buildingCode.valueChanges.subscribe(
+        buildingCode => {
+          const values = this.service.formGroup.getRawValue();
+
+          if (values.buildingCode !== buildingCode) {
+            this.service.formGroup.patchValue({buildingCode});
+          }
+        }
+      )
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub.unsubscribe());
+  }
+}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.html b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..983a81479ea0da1880d24090cb9e5a4d27c53641
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.html
@@ -0,0 +1,69 @@
+<!-- Control Panel -->
+<form
+  class="height-full overflow-auto padding-top-1"
+  [formGroup]="formGroup"
+  (submit)="onSubmit()"
+>
+  <!-- Building code -->
+  <app-building-code-control [control]="formGroup.controls.buildingCode" />
+
+  <!-- Model -->
+  <nshmp-lib-ng-hazard-model-form
+    [modelControl]="formGroup.controls.model"
+    [models]="service.availableModels()"
+  />
+
+  @if (service.hazardUsage()) {
+    @let usage = service.hazardUsage();
+
+    <!-- Latitude -->
+    <nshmp-lib-ng-hazard-location-form
+      class="latitude-input"
+      [locationControl]="formGroup.controls.latitude"
+      label="Latitude"
+      [bounds]="usage?.response?.models?.latitude"
+    />
+
+    <!-- Longitude -->
+    <nshmp-lib-ng-hazard-location-form
+      class="longitude-input"
+      [locationControl]="formGroup.controls.longitude"
+      label="Longitude"
+      [bounds]="usage.response?.models?.longitude"
+    />
+  }
+
+  <!-- Select site -->
+  <nshmp-lib-ng-map-select-site
+    [data]="selectSiteData()"
+    (siteSelect)="setLocation($event)"
+  />
+
+  <!-- Site Class -->
+  @if (siteClasses()) {
+    <nshmp-lib-ng-hazard-site-class-form
+      [siteClassControl]="formGroup.controls.siteClass"
+      [siteClasses]="siteClasses()"
+      [modelControl]="formGroup.controls.model"
+    />
+  }
+
+  <!-- Imt -->
+  <mat-form-field class="grid-col-12 imt-select">
+    <mat-label> Intensity Measure Type </mat-label>
+    <mat-select [formControl]="formGroup.controls.imt">
+      @for (imt of imts(); track imt) {
+        <mat-option [value]="imt?.value">
+          {{ imt?.display }}
+        </mat-option>
+      }
+    </mat-select>
+  </mat-form-field>
+
+  <nshmp-lib-ng-control-panel-buttons
+    [plotDisabled]="formGroup.invalid"
+    [serviceCallInfo]="service.serviceCallInfo()"
+    [resetDisabled]="formGroup.pristine"
+    (resetClick)="service.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.scss b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.spec.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a1868b90816108ce53233ee046a94355b7432221
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.spec.ts
@@ -0,0 +1,30 @@
+import {provideHttpClient} from '@angular/common/http';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
+import {provideRouter} from '@angular/router';
+
+import {ControlPanelHazardComponent} from './control-panel-hazard.component';
+
+describe('ControlPanelHazardComponent', () => {
+  let component: ControlPanelHazardComponent;
+  let fixture: ComponentFixture<ControlPanelHazardComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ControlPanelHazardComponent],
+      providers: [
+        provideHttpClient(),
+        provideNoopAnimations(),
+        provideRouter([]),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ControlPanelHazardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f980f7d8cfda10a27917fd8311c63abd750a5bf
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-hazard/control-panel-hazard.component.ts
@@ -0,0 +1,152 @@
+import {AsyncPipe} from '@angular/common';
+import {Component, computed, OnDestroy, OnInit, Signal} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
+import {MatFormFieldModule} from '@angular/material/form-field';
+import {MatSelectModule} from '@angular/material/select';
+import {
+  NshmpLibNgHazardLocationFormComponent,
+  NshmpLibNgHazardModelFormComponent,
+  NshmpLibNgHazardSiteClassFormComponent,
+} from '@ghsc/nshmp-lib-ng/hazard';
+import {
+  NshmpLibNgMapSelectSiteComponent,
+  SelectSiteDialogData,
+} from '@ghsc/nshmp-lib-ng/map';
+import {
+  NshmpLibNgControlPanelButtonsComponent,
+  nshmpUtils,
+  RETURN_PERIOD_BOUNDS,
+  RETURN_PERIODS,
+} from '@ghsc/nshmp-lib-ng/nshmp';
+import {Location} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/geo';
+import {environment} from 'projects/nshmp-apps/src/environments/environment';
+import {Subscription} from 'rxjs';
+
+import {AppService} from '../../services/app.service';
+import {BuildingCodeControlComponent} from '../building-code-control/building-code-control.component';
+
+@Component({
+  imports: [
+    NshmpLibNgHazardModelFormComponent,
+    NshmpLibNgHazardLocationFormComponent,
+    NshmpLibNgMapSelectSiteComponent,
+    NshmpLibNgHazardSiteClassFormComponent,
+    NshmpLibNgControlPanelButtonsComponent,
+    AsyncPipe,
+    MatFormFieldModule,
+    ReactiveFormsModule,
+    MatSelectModule,
+    BuildingCodeControlComponent,
+  ],
+  selector: 'app-control-panel-hazard',
+  standalone: true,
+  styleUrl: './control-panel-hazard.component.scss',
+  templateUrl: './control-panel-hazard.component.html',
+})
+export class ControlPanelHazardComponent implements OnInit, OnDestroy {
+  /** Max and min bounds for return periods */
+  returnPeriodBounds = RETURN_PERIOD_BOUNDS;
+  /** List of return periods */
+  returnPeriods = RETURN_PERIODS;
+  /** Site class select menu holder */
+  siteClassPlaceHolder = nshmpUtils.selectPlaceHolder();
+
+  /** Form field state */
+  formGroup = this.service.hazardFormGroup;
+
+  /** Data for select site component */
+  selectSiteData: Signal<SelectSiteDialogData> = computed(() => {
+    const usage = this.service.hazardUsage();
+    const nshmService = this.service.nshmService();
+
+    if (usage === null || nshmService === undefined) {
+      return;
+    }
+
+    const services = environment.webServices.nshmpHazWs.services.curveServices;
+
+    return {
+      mapUrl: `${nshmService.url}${services.map}?raw=true`,
+      nshm: this.service.hazardFormGroup.getRawValue().model,
+      sitesUrl: `${nshmService.url}${services.sites}?raw=true`,
+    };
+  });
+
+  /** List of site class `Parameter`s */
+  siteClasses = computed(
+    () => this.service.hazardUsage()?.response?.models.siteClasses
+  );
+
+  /** List of imt `Parameter`s */
+  imts = computed(() => this.service.hazardUsage()?.response?.models.imts);
+
+  private subs: Subscription[] = [];
+
+  constructor(public service: AppService) {}
+
+  ngOnInit(): void {
+    const controls = this.formGroup.controls;
+
+    this.subs.push(
+      controls.latitude.valueChanges.subscribe(() => this.service.resetState())
+    );
+
+    this.subs.push(
+      controls.longitude.valueChanges.subscribe(() => this.service.resetState())
+    );
+
+    this.subs.push(
+      controls.model.valueChanges.subscribe(() => this.onModelChange())
+    );
+
+    this.subs.push(
+      controls.imt.valueChanges.subscribe(() => this.service.hazardToRtgm())
+    );
+
+    this.subs.push(
+      controls.siteClass.valueChanges.subscribe(() =>
+        this.service.hazardToRtgm()
+      )
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub.unsubscribe());
+  }
+
+  onSubmit() {
+    this.service.callHazardService();
+  }
+
+  setLocation(location: Location): void {
+    this.service.hazardFormGroup.patchValue({
+      latitude: location.latitude,
+      longitude: location.longitude,
+    });
+  }
+
+  private onModelChange(): void {
+    const {latitude, longitude} = this.formGroup.getRawValue();
+    const latitudeBounds = this.service.hazardUsage().response.models.latitude;
+    const longitudeBounds =
+      this.service.hazardUsage().response.models.longitude;
+
+    if (
+      !(
+        latitude >= latitudeBounds.min &&
+        latitude <= latitudeBounds.max &&
+        longitude >= longitudeBounds.min &&
+        longitude <= longitudeBounds.max
+      )
+    ) {
+      this.formGroup.patchValue({
+        latitude: NaN,
+        longitude: NaN,
+      });
+    }
+
+    this.formGroup.controls.latitude.markAsPristine();
+    this.formGroup.controls.longitude.markAsPristine();
+    this.service.resetState();
+  }
+}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.html b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4ac652ac54d9c398c7c0378b3df7033a3b5f3a62
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.html
@@ -0,0 +1,48 @@
+<form
+  [formGroup]="formGroup"
+  class="height-full overflow-auto padding-top-1"
+  (submit)="service.callService()"
+>
+  <!-- Building code -->
+  <app-building-code-control [control]="formGroup.controls.buildingCode" />
+
+  <!-- Label -->
+  <mat-form-field class="grid-col-12 margin-bottom-05">
+    <mat-label>{{ parameters()?.label.label }}</mat-label>
+    <input matInput type="text" [formControl]="formGroup.controls.label" />
+    <mat-hint>{{ parameters()?.label.info }}</mat-hint>
+  </mat-form-field>
+
+  <!-- IMLs -->
+  <mat-form-field class="grid-col-12 margin-bottom-05">
+    <mat-label>
+      {{ parameters()?.iml.label }} ({{ parameters()?.iml.units }})
+    </mat-label>
+    <input matInput type="text" [formControl]="formGroup.controls.iml" />
+    <mat-hint>{{ parameters()?.iml.info }}</mat-hint>
+    @if (formGroup.controls.iml.errors?.imlNotSameLength) {
+      <mat-error>Must be same length as AFEs</mat-error>
+    } @else {
+      <mat-error>Must be a comma delimited list of numbers</mat-error>
+    }
+  </mat-form-field>
+
+  <!-- AFEs -->
+  <mat-form-field class="grid-col-12 margin-bottom-05">
+    <mat-label>{{ parameters()?.afe.label }}</mat-label>
+    <input matInput type="text" [formControl]="formGroup.controls.afe" />
+    <mat-hint class="hint">{{ parameters()?.afe.info }}</mat-hint>
+    @if (formGroup.controls.afe.errors?.afeNotSameLength) {
+      <mat-error>Must be same length as IMLs</mat-error>
+    } @else {
+      <mat-error>Must be a comma delimited list of numbers</mat-error>
+    }
+  </mat-form-field>
+
+  <nshmp-lib-ng-control-panel-buttons
+    [plotDisabled]="formGroup.invalid"
+    [serviceCallInfo]="service.serviceCallInfo()"
+    [resetDisabled]="formGroup.pristine"
+    (resetClick)="service.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.scss b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..479ce114e334096c48ce47bfb3705bd69ef28ea9
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.scss
@@ -0,0 +1,3 @@
+.hint {
+  font-size: 10.9px;
+}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.spec.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ffcadf39d54a28a0250234fdbe31657c71a78bdb
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.spec.ts
@@ -0,0 +1,30 @@
+import {provideHttpClient} from '@angular/common/http';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
+import {provideRouter} from '@angular/router';
+
+import {ControlPanelInputComponent} from './control-panel-input.component';
+
+describe('ControlPanelInputComponent', () => {
+  let component: ControlPanelInputComponent;
+  let fixture: ComponentFixture<ControlPanelInputComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ControlPanelInputComponent],
+      providers: [
+        provideHttpClient(),
+        provideNoopAnimations(),
+        provideRouter([]),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ControlPanelInputComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73a25df9f0732bdce0a0f829ba512ea0a9ffb032
--- /dev/null
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel-input/control-panel-input.component.ts
@@ -0,0 +1,63 @@
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
+import {MatButtonModule} from '@angular/material/button';
+import {MatDivider} from '@angular/material/divider';
+import {MatIcon} from '@angular/material/icon';
+import {MatError, MatHint, MatInputModule} from '@angular/material/input';
+import {MatFormField, MatSelectModule} from '@angular/material/select';
+import {NshmpLibNgControlPanelButtonsComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {Subscription} from 'rxjs';
+
+import {AppService} from '../../services/app.service';
+import {BuildingCodeControlComponent} from '../building-code-control/building-code-control.component';
+
+@Component({
+  imports: [
+    MatFormField,
+    MatSelectModule,
+    MatInputModule,
+    ReactiveFormsModule,
+    MatIcon,
+    MatButtonModule,
+    MatDivider,
+    NshmpLibNgControlPanelButtonsComponent,
+    MatHint,
+    MatError,
+    BuildingCodeControlComponent,
+  ],
+  selector: 'app-control-panel-input',
+  standalone: true,
+  styleUrl: './control-panel-input.component.scss',
+  templateUrl: './control-panel-input.component.html',
+})
+export class ControlPanelInputComponent implements OnInit, OnDestroy {
+  formGroup = this.service.formGroup;
+
+  parameters = computed(() => this.service.usage()?.response.parameters);
+
+  subs: Subscription[] = [];
+
+  constructor(public service: AppService) {}
+
+  ngOnInit(): void {
+    const controls = this.formGroup.controls;
+
+    this.subs.push(
+      controls.afe.valueChanges.subscribe(() => this.service.resetState())
+    );
+
+    this.subs.push(
+      controls.buildingCode.valueChanges.subscribe(() =>
+        this.service.resetState()
+      )
+    );
+
+    this.subs.push(
+      controls.iml.valueChanges.subscribe(() => this.service.resetState())
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub.unsubscribe());
+  }
+}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.html
index eb8c7c4bc32957b4f35195484f8ef619bf87c09c..146d47c37be1819c282ff11a4fecc7b3fe087cb3 100644
--- a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.html
@@ -1,59 +1,9 @@
-<form
-  [formGroup]="formGroup"
-  class="height-full overflow-auto padding-top-1"
-  (submit)="service.callService()"
->
-  <!-- Label -->
-  <mat-form-field class="grid-col-12 margin-bottom-05">
-    <mat-label>{{ parameters()?.label.label }}</mat-label>
-    <input matInput type="text" [formControl]="formGroup.controls.label" />
-    <mat-hint>{{ parameters()?.label.info }}</mat-hint>
-  </mat-form-field>
-
-  <!-- Building code -->
-  <mat-form-field class="grid-col-12 margin-bottom-3">
-    <mat-label>{{ parameters()?.building_code.label }}</mat-label>
-
-    <mat-select [formControl]="formGroup.controls.buildingCode">
-      @for (buildingCode of buildingCodes(); track buildingCode) {
-        <mat-option [value]="buildingCode">
-          {{ buildingCode }}
-        </mat-option>
-      }
-    </mat-select>
-    <mat-hint>{{ parameters()?.building_code.info }}</mat-hint>
-  </mat-form-field>
-
-  <!-- IMLs -->
-  <mat-form-field class="grid-col-12 margin-bottom-05">
-    <mat-label>
-      {{ parameters()?.iml.label }} ({{ parameters()?.iml.units }})
-    </mat-label>
-    <input matInput type="text" [formControl]="formGroup.controls.iml" />
-    <mat-hint>{{ parameters()?.iml.info }}</mat-hint>
-    @if (formGroup.controls.iml.errors?.imlNotSameLength) {
-      <mat-error>Must be same length as AFEs</mat-error>
-    } @else {
-      <mat-error>Must be a comma delimited list of numbers</mat-error>
-    }
-  </mat-form-field>
-
-  <!-- AFEs -->
-  <mat-form-field class="grid-col-12 margin-bottom-05">
-    <mat-label>{{ parameters()?.afe.label }}</mat-label>
-    <input matInput type="text" [formControl]="formGroup.controls.afe" />
-    <mat-hint class="hint">{{ parameters()?.afe.info }}</mat-hint>
-    @if (formGroup.controls.afe.errors?.afeNotSameLength) {
-      <mat-error>Must be same length as IMLs</mat-error>
-    } @else {
-      <mat-error>Must be a comma delimited list of numbers</mat-error>
-    }
-  </mat-form-field>
-
-  <nshmp-lib-ng-control-panel-buttons
-    [plotDisabled]="formGroup.invalid"
-    [serviceCallInfo]="service.serviceCallInfo()"
-    [resetDisabled]="formGroup.pristine"
-    (resetClick)="service.resetControlPanel()"
-  />
-</form>
+<mat-tab-group class="height-full">
+  <mat-tab label="Manual Input" bodyClass="padding-top-2">
+    <app-control-panel-input />
+  </mat-tab>
+
+  <mat-tab label="Hazard Calc" bodyClass="padding-top-2">
+    <app-control-panel-hazard />
+  </mat-tab>
+</mat-tab-group>
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.scss b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.scss
index 479ce114e334096c48ce47bfb3705bd69ef28ea9..9a03f6e52f9d722453770e186cdc7c6936e1885c 100644
--- a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.scss
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.scss
@@ -1,3 +1,3 @@
-.hint {
-  font-size: 10.9px;
+.mat-mdc-tab-body-wrapper {
+  height: 100%;
 }
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.ts
index 319278c965a0261ad84c1821832ade650800a12e..932848e3ce3fa10e1f3684da438f2e331c64d045 100644
--- a/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/components/control-panel/control-panel.component.ts
@@ -1,63 +1,19 @@
-import {Component, computed, OnDestroy, OnInit} from '@angular/core';
-import {ReactiveFormsModule} from '@angular/forms';
-import {MatButtonModule} from '@angular/material/button';
-import {MatDivider} from '@angular/material/divider';
-import {MatIcon} from '@angular/material/icon';
-import {MatError, MatHint, MatInputModule} from '@angular/material/input';
-import {MatFormField, MatSelectModule} from '@angular/material/select';
-import {NshmpLibNgControlPanelButtonsComponent} from '@ghsc/nshmp-lib-ng/nshmp';
-import {Subscription} from 'rxjs';
+import {Component, ViewEncapsulation} from '@angular/core';
+import {MatTabsModule} from '@angular/material/tabs';
 
-import {AppService} from '../../services/app.service';
+import {ControlPanelHazardComponent} from '../control-panel-hazard/control-panel-hazard.component';
+import {ControlPanelInputComponent} from '../control-panel-input/control-panel-input.component';
 
 @Component({
+  encapsulation: ViewEncapsulation.None,
   imports: [
-    MatFormField,
-    MatSelectModule,
-    MatInputModule,
-    ReactiveFormsModule,
-    MatIcon,
-    MatButtonModule,
-    MatDivider,
-    NshmpLibNgControlPanelButtonsComponent,
-    MatHint,
-    MatError,
+    MatTabsModule,
+    ControlPanelInputComponent,
+    ControlPanelHazardComponent,
   ],
   selector: 'app-control-panel',
   standalone: true,
   styleUrl: './control-panel.component.scss',
   templateUrl: './control-panel.component.html',
 })
-export class ControlPanelComponent implements OnInit, OnDestroy {
-  formGroup = this.service.formGroup;
-
-  buildingCodes = computed(
-    () => this.service.usage()?.response.parameters.building_code.values
-  );
-
-  parameters = computed(() => this.service.usage()?.response.parameters);
-
-  subs: Subscription[] = [];
-
-  constructor(public service: AppService) {}
-
-  ngOnInit(): void {
-    const controls = this.formGroup.controls;
-
-    this.subs.push(
-      controls.afe.valueChanges.subscribe(() => this.service.resetState())
-    );
-    this.subs.push(
-      controls.buildingCode.valueChanges.subscribe(() =>
-        this.service.resetState()
-      )
-    );
-    this.subs.push(
-      controls.iml.valueChanges.subscribe(() => this.service.resetState())
-    );
-  }
-
-  ngOnDestroy(): void {
-    this.subs.forEach(sub => sub.unsubscribe());
-  }
-}
+export class ControlPanelComponent {}
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/models/state.model.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/models/state.model.ts
index f11fbd878c52bc4053ed9cbf8e1d0642bbdef2c9..544b4cb5df5b6fa9c4e1771cf2d847558b819dcb 100644
--- a/projects/nshmp-apps/src/app/designmaps/rtgm/models/state.model.ts
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/models/state.model.ts
@@ -3,10 +3,24 @@ import {
   RtgmCalcResponse,
   RtgmUsageResponse,
 } from '@ghsc/nshmp-utils-ts/libs/erp/rtgm';
+import {
+  StaticHazardResponse,
+  StaticHazardUsage,
+} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-static/hazard-service';
+import {StaticNshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-static/nshm-service';
+import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
 
 import {RtgmPlots} from './plots.model';
 
 export interface AppState {
+  /** Available NSHMs */
+  availableModels: Parameter[];
+  /** Hazard service responses */
+  hazardServiceResponse: StaticHazardResponse;
+  /** Hazard usage responses */
+  hazardUsageResponses: Map<string, StaticHazardUsage>;
+  /** NSHM services */
+  nshmServices: StaticNshmMetadata[];
   /** RTGM plots */
   plots: RtgmPlots;
   /** Service call info */
diff --git a/projects/nshmp-apps/src/app/designmaps/rtgm/services/app.service.ts b/projects/nshmp-apps/src/app/designmaps/rtgm/services/app.service.ts
index cc8b8f5e6a754e766db950f518d47d787421bc0a..0b11d9762012499a700355ee95162362b07f8d98 100644
--- a/projects/nshmp-apps/src/app/designmaps/rtgm/services/app.service.ts
+++ b/projects/nshmp-apps/src/app/designmaps/rtgm/services/app.service.ts
@@ -2,6 +2,12 @@ import {HttpParams} from '@angular/common/http';
 import {computed, Injectable, Signal, signal} from '@angular/core';
 import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
 import {ActivatedRoute, Router} from '@angular/router';
+import {
+  HazardControlForm,
+  HazardService,
+  hazardUtils,
+  StaticNshms,
+} from '@ghsc/nshmp-lib-ng/hazard';
 import {
   NshmpService,
   ServiceCallInfo,
@@ -17,6 +23,16 @@ import {
 } from '@ghsc/nshmp-utils-ts/libs/erp/rtgm';
 import {Maths} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/calc';
 import {XySequence} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data';
+import {Imt} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm';
+import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm';
+import {
+  HazardResponseMetadata,
+  StaticHazardResponse,
+  StaticHazardUsage,
+  StaticResponseData,
+} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-static/hazard-service';
+import {StaticNshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-static/nshm-service';
+import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
 import * as d3Color from 'd3-scale-chromatic';
 import deepEqual from 'deep-equal';
 import {AxisType, PlotData} from 'plotly.js';
@@ -24,7 +40,7 @@ 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 {catchError, forkJoin} from 'rxjs';
 
 import {ControlForm} from '../models/control-form.model';
 import {RtgmPlots} from '../models/plots.model';
@@ -48,6 +64,11 @@ export interface IterationPlotData {
   integralHazardFragilityPlotData: Partial<PlotData>[];
 }
 
+export interface RtgmHazardControlForm extends HazardControlForm {
+  buildingCode: string;
+  imt: Imt;
+}
+
 @Injectable({
   providedIn: 'root',
 })
@@ -55,7 +76,13 @@ export class AppService
   extends SharedService
   implements AppServiceModel<AppState, ControlForm>
 {
+  /** nshmp-haz-ws web config */
+  private nshmpWsStatic = environment.webServices.nshmpWsStatic;
+
   private aspectRatio = '21:9';
+  /** Hazard endpoint */
+  private hazardServiceEndpoint =
+    this.nshmpWsStatic.services.curveServices.hazard;
   /** RTGM web config */
   private rtgm = environment.webServices.rtgm;
   /** RTGM calc url */
@@ -63,6 +90,11 @@ export class AppService
 
   /** Form group */
   formGroup = this.formBuilder.group<ControlForm>(this.defaultFormValues());
+  hazardFormGroup = this.formBuilder.group<RtgmHazardControlForm>({
+    ...hazardUtils.hazardDefaultFormValues(),
+    buildingCode: this.defaultFormValues().buildingCode,
+    imt: Imt.PGA,
+  });
   /** App state */
   state = signal<AppState>(this.initialState());
 
@@ -71,12 +103,40 @@ export class AppService
     private spinnerService: SpinnerService,
     private nshmpService: NshmpService,
     private route: ActivatedRoute,
-    private router: Router
+    private router: Router,
+    private hazardService: HazardService
   ) {
     super();
     this.addValidators();
   }
 
+  /**
+   * Returns the available models.
+   */
+  get availableModels(): Signal<Parameter[]> {
+    return computed(() => this.state().availableModels);
+  }
+
+  get hazardUsage(): Signal<StaticHazardUsage> {
+    return computed(() =>
+      this.state().hazardUsageResponses.get(
+        this.hazardFormGroup.getRawValue().model
+      )
+    );
+  }
+
+  /**
+   * Returns the metadata of the NSHM observable.
+   */
+  get nshmService(): Signal<StaticNshmMetadata> {
+    return computed(() =>
+      this.state().nshmServices.find(
+        nshmService =>
+          nshmService.model === this.hazardFormGroup.getRawValue().model
+      )
+    );
+  }
+
   /**
    * Returns the plots.
    */
@@ -155,6 +215,31 @@ export class AppService
     this.formGroup.controls.label.addValidators(required);
   }
 
+  callHazardService(): void {
+    const spinnerRef = this.spinnerService.show('Calling hazard service ...');
+    const values = this.hazardFormGroup.getRawValue();
+
+    const service = this.state().nshmServices.find(
+      nshmService => nshmService.model === values.model
+    );
+    const serviceUrl = `${service.url}${this.hazardServiceEndpoint}`;
+    const url = `${serviceUrl}/${values.longitude}/${values.latitude}`;
+
+    this.nshmpService
+      .callService$<StaticHazardResponse>(url)
+      .pipe(
+        catchError((error: Error) => {
+          spinnerRef.close();
+          return this.nshmpService.throwError$(error);
+        })
+      )
+      .subscribe(hazardServiceResponse => {
+        spinnerRef.close();
+        this.updateState({hazardServiceResponse});
+        this.hazardToRtgm();
+      });
+  }
+
   callService(): void {
     const spinnerRef = this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
 
@@ -211,28 +296,60 @@ export class AppService
     };
   }
 
+  hazardToRtgm(): void {
+    const values = this.hazardFormGroup.getRawValue();
+
+    const responseData = this.getHazardResponseData(this.state(), values);
+
+    if (responseData === null || responseData === undefined) {
+      return;
+    }
+
+    this.formGroup.patchValue({
+      afe: responseData.data.ys.join(','),
+      iml: responseData.data.xs.join(','),
+      label: `${values.model} ${values.siteClass} ${values.imt}`,
+    });
+
+    this.callService();
+  }
+
   init(): void {
     const spinnerRef = this.spinnerService.show(
       SpinnerService.MESSAGE_METADATA
     );
 
-    this.nshmpService
-      .callService$<RtgmUsageResponse>(this.serviceUrl)
+    const rtgm = this.nshmpService.callService$<RtgmUsageResponse>(
+      this.serviceUrl
+    );
+    const hazard = this.hazardService.staticNshms$(
+      `${this.nshmpWsStatic.url}${this.nshmpWsStatic.services.nshms}`,
+      this.hazardServiceEndpoint
+    );
+
+    forkJoin([rtgm, hazard])
       .pipe(
         catchError((error: Error) => {
           spinnerRef.close();
           return this.nshmpService.throwError$(error);
         })
       )
-      .subscribe(usageResponse => {
-        this.handleUsageResponse(usageResponse);
+      .subscribe(([usageResponse, nshms]) => {
+        this.handleUsageResponse(usageResponse, nshms);
         this.initialFormSet();
         spinnerRef.close();
       });
   }
 
   initialState(): AppState {
+    const hazardUsageResponses: Map<string, StaticHazardUsage> = new Map();
+    hazardUsageResponses.set(NshmId.CONUS_2018, null);
+
     return {
+      availableModels: [],
+      hazardServiceResponse: null,
+      hazardUsageResponses,
+      nshmServices: [],
       plots: this.defaultPlots(),
       serviceCallInfo: {
         serviceCalls: [],
@@ -277,6 +394,7 @@ export class AppService
 
   resetState(): void {
     this.updateState({
+      hazardServiceResponse: null,
       plots: this.defaultPlots(),
       serviceCallInfo: {
         ...this.state().serviceCallInfo,
@@ -659,6 +777,28 @@ export class AppService
     };
   }
 
+  /**
+   * Returns the response data for a site class.
+   *
+   * @param state The current state
+   */
+  private getHazardResponseData(
+    state: AppState,
+    values: RtgmHazardControlForm
+  ): StaticResponseData<HazardResponseMetadata> {
+    const siteClass = values.siteClass;
+
+    const responseData = state.hazardServiceResponse
+      ? state.hazardServiceResponse.response.find(response => {
+          return response.find(data => data.metadata.siteClass === siteClass);
+        })
+      : null;
+
+    return responseData?.find(
+      data => data.metadata.imt.value === values.imt.toString()
+    );
+  }
+
   private handleServiceResponse(serviceResponse: RtgmCalcResponse): void {
     this.updateState({
       serviceCallInfo: {
@@ -671,8 +811,14 @@ export class AppService
     this.createPlots();
   }
 
-  private handleUsageResponse(usageResponse: RtgmUsageResponse): void {
+  private handleUsageResponse(
+    usageResponse: RtgmUsageResponse,
+    nshms: StaticNshms
+  ): void {
     this.updateState({
+      availableModels: nshms.models,
+      hazardUsageResponses: nshms.usageResponses,
+      nshmServices: nshms.nshmServices,
       serviceCallInfo: {
         ...this.state().serviceCallInfo,
         usage: [this.serviceUrl],
diff --git a/projects/nshmp-apps/src/app/services/components/content/content.component.ts b/projects/nshmp-apps/src/app/services/components/content/content.component.ts
index 2f9ee7dcc31266216a44b629e4e32f9b7524e689..0d9ec4aa2a84861101ae801ba7bb493914f3440e 100644
--- a/projects/nshmp-apps/src/app/services/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/services/components/content/content.component.ts
@@ -175,6 +175,29 @@ export class ContentComponent implements AfterViewInit {
       subtitle: 'Pre-Computed and Published Hazard Curves',
       title: 'NSHM Static Hazard Curve Services',
     },
+    // RTGM services
+    {
+      applicationsUsedIn: [apps().designMaps.rtgm],
+      id: ServiceGroupId.RTGM,
+      images: [
+        {
+          darkModeImage: 'rtgm-derivative-dark-mode.webp',
+          image: 'rtgm-derivative.webp',
+        },
+        {
+          darkModeImage: 'rtgm-integral-dark-mode.webp',
+          image: 'rtgm-integral.webp',
+        },
+      ],
+      services$: of([
+        {
+          ...environment.webServices.rtgm,
+          swaggerEndPoint: environment.webServices.rtgm.services.swagger,
+        },
+      ]),
+      subtitle: 'RTGM from hazard curves',
+      title: 'Risk-Targeted Ground Motion Service',
+    },
     // NCM services
     {
       applicationsUsedIn: [],
@@ -210,20 +233,6 @@ export class ContentComponent implements AfterViewInit {
       subtitle: 'Risk-Targeted Desgin Response Spectra for 2023 Edition',
       title: 'AASHTO 2023 Services',
     },
-    // RTGM services
-    {
-      applicationsUsedIn: [],
-      id: ServiceGroupId.RTGM,
-      images: [],
-      services$: of([
-        {
-          ...environment.webServices.rtgm,
-          swaggerEndPoint: environment.webServices.rtgm.services.swagger,
-        },
-      ]),
-      subtitle: 'RTGM from hazard curves',
-      title: 'Risk-Targeted Ground Motion Service',
-    },
   ];
 
   constructor(
diff --git a/projects/nshmp-apps/src/assets/services/rtgm-derivative-dark-mode.webp b/projects/nshmp-apps/src/assets/services/rtgm-derivative-dark-mode.webp
new file mode 100644
index 0000000000000000000000000000000000000000..db9d0f7a99ebf8630813f3a0c1c838b623b55deb
Binary files /dev/null and b/projects/nshmp-apps/src/assets/services/rtgm-derivative-dark-mode.webp differ
diff --git a/projects/nshmp-apps/src/assets/services/rtgm-derivative.webp b/projects/nshmp-apps/src/assets/services/rtgm-derivative.webp
new file mode 100644
index 0000000000000000000000000000000000000000..b248fed3e14b7266f17e8d4f6c0783f52ce0a03c
Binary files /dev/null and b/projects/nshmp-apps/src/assets/services/rtgm-derivative.webp differ
diff --git a/projects/nshmp-apps/src/assets/services/rtgm-integral-dark-mode.webp b/projects/nshmp-apps/src/assets/services/rtgm-integral-dark-mode.webp
new file mode 100644
index 0000000000000000000000000000000000000000..334dde551b0dd33643fbefff7fcfe9f014a4b896
Binary files /dev/null and b/projects/nshmp-apps/src/assets/services/rtgm-integral-dark-mode.webp differ
diff --git a/projects/nshmp-apps/src/assets/services/rtgm-integral.webp b/projects/nshmp-apps/src/assets/services/rtgm-integral.webp
new file mode 100644
index 0000000000000000000000000000000000000000..cef31a580eee6243d1fbe0a29aee6e2f42a5df85
Binary files /dev/null and b/projects/nshmp-apps/src/assets/services/rtgm-integral.webp differ