packages/miew/src/io/exporters/PDBExporter.js

Summary

Maintainability
C
7 hrs
Test Coverage
import { invert } from 'lodash'
import Complex from '../../chem/Complex'
import Exporter from './Exporter'
import PDBResult from './PDBResult'
import Assembly from '../../chem/Assembly'
import { typeByPDBHelixClass } from '../../chem/Helix'

export default class PDBExporter extends Exporter {
  constructor(source, options) {
    super(source, options)
    this._tags = [
      'HEADER',
      'TITLE',
      'COMPND',
      'REMARK',
      'HELIX',
      'SHEET',
      'ATOM and HETATM',
      'CONECT'
    ]
    this._result = null
    this._tagExtractors = {
      HEADER: this._extractHEADER,
      TITLE: this._extractTITLE,
      'ATOM and HETATM': this._extractATOM,
      CONECT: this._extractCONECT,
      COMPND: this._extractCOMPND,
      REMARK: this._extractREMARK,
      HELIX: this._extractHELIX,
      SHEET: this._extractSHEET
    }
    this._stringForRemark350 =
      'COORDINATES FOR A COMPLETE MULTIMER REPRESENTING THE KNOWN\n' +
      'BIOLOGICALLY SIGNIFICANT OLIGOMERIZATION STATE OF THE\n' +
      'MOLECULE CAN BE GENERATED BY APPLYING BIOMT TRANSFORMATIONS\n' +
      'GIVEN BELOW.  BOTH NON-CRYSTALLOGRAPHIC AND\n' +
      'CRYSTALLOGRAPHIC OPERATIONS ARE GIVEN.'

    this._stringForRemark290 =
      'CRYSTALLOGRAPHIC SYMMETRY TRANSFORMATIONS\n' +
      'THE FOLLOWING TRANSFORMATIONS OPERATE ON THE ATOM/HETATM\n' +
      'RECORDS IN THIS ENTRY TO PRODUCE CRYSTALLOGRAPHICALLY\n' +
      'RELATED MOLECULES.'
  }

  exportSync() {
    const result = new PDBResult()
    if (!this._source) {
      return this._result
    }

    for (let i = 0; i < this._tags.length; i++) {
      const tag = this._tags[i]
      const func = this._tagExtractors[tag]
      if (typeof func === 'function') {
        func.call(this, result)
      }
    }

    this._result = result.getResult()

    return this._result
  }

  _extractHEADER(result) {
    if (!this._source.metadata) {
      return
    }
    const { metadata } = this._source
    result.newTag('HEADER')
    result.newString()
    if (metadata.classification) {
      result.writeString(metadata.classification, 11, 50)
    }
    if (metadata.date) {
      result.writeString(metadata.date, 51, 59)
    }
    if (metadata.id) {
      result.writeString(metadata.id, 63, 66)
    }
  }

  _extractTITLE(result) {
    if (!this._source.metadata) {
      return
    }
    const { metadata } = this._source
    if (!metadata.title) {
      return
    }
    result.newTag('TITLE', true)
    for (let i = 0; i < metadata.title.length; i++) {
      result.newString()
      result.writeString(metadata.title[i], 11, 80)
    }
  }

  _extractCONECT(result) {
    if (!this._source._atoms) {
      return
    }

    const atoms = this._source._atoms
    result.newTag('CONECT')

    for (let i = 0; i < atoms.length; i++) {
      const fixedBonds = atoms[i].bonds.filter((bond) => bond._fixed)
      if (fixedBonds.length !== 0) {
        result.writeBondsArray(fixedBonds.reverse(), atoms[i])
      }
    }
  }

  _extractSHEET(result) {
    if (!this._source._sheets) {
      return
    }

    result.newTag('SHEET')

    const sheets = this._source._sheets
    for (let i = 0; i < sheets.length; i++) {
      if (sheets[i]._strands) {
        const strands = sheets[i]._strands
        for (let j = 0; j < strands.length; j++) {
          result.newString()
          result.writeString(j + 1, 10, 8)
          result.writeString(sheets[i]._name, 14, 12)
          result.writeString(strands.length, 16, 15)
          result.writeString(strands[j].init._type._name, 18, 20)
          result.writeString(strands[j].init._chain._name, 22, 22)
          result.writeString(strands[j].init._sequence, 26, 23)
          result.writeString(strands[j].init._icode, 27, 27)
          result.writeString(strands[j].term._type._name, 29, 31)
          result.writeString(strands[j].init._chain._name, 33, 33)
          result.writeString(strands[j].term._sequence, 37, 34)
          result.writeString(strands[j].term._icode, 38, 38)
          result.writeString(strands[j].sense, 40, 39)
        }
      }
    }
  }

  _extractHELIX(result) {
    if (!this._source._helices) {
      return
    }

    result.newTag('HELIX')
    const helices = this._source._helices
    for (let i = 0; i < helices.length; i++) {
      const helix = helices[i]
      const helixClass = invert(typeByPDBHelixClass)
      result.newString()
      result.writeString(helix.serial, 10, 8)
      result.writeString(helix.name, 14, 12)
      result.writeString(helix.init._type._name, 16, 18)
      result.writeString(helix.init._chain._name, 20, 20)
      result.writeString(helix.init._sequence, 25, 22)
      result.writeString(helix.init._icode, 26, 26)
      result.writeString(helix.term._type._name, 28, 30)
      result.writeString(helix.term._chain._name, 32, 32)
      result.writeString(helix.term._sequence, 37, 34)
      result.writeString(helix.term._icode, 38, 38)
      result.writeString(helixClass[helix.type], 40, 39)
      result.writeString(helix.comment, 41, 70)
      result.writeString(helix.length, 76, 72)
    }
  }

  _extractATOM(result) {
    if (!this._source._atoms) {
      return
    }
    const atoms = this._source._atoms

    for (let i = 0; i < atoms.length; i++) {
      const tag = atoms[i].het ? 'HETATM' : 'ATOM'
      result.newString(tag)
      const startIndx =
        atoms[i].element.name.length > 1 || atoms[i].name.length > 3 ? 13 : 14
      result.writeString(atoms[i].serial, 11, 7)
      result.writeString(atoms[i].name, startIndx, 16)
      result.writeString(String.fromCharCode(atoms[i].location), 17, 17)
      result.writeString(atoms[i].residue._type._name, 20, 18)
      result.writeString(atoms[i].residue._chain._name, 22, 22)
      result.writeString(atoms[i].residue._sequence, 26, 23)
      result.writeString(atoms[i].residue._icode, 27, 27)
      result.writeString(atoms[i].position.x.toFixed(3), 38, 31)
      result.writeString(atoms[i].position.y.toFixed(3), 46, 39)
      result.writeString(atoms[i].position.z.toFixed(3), 54, 47)
      result.writeString(atoms[i].occupancy.toFixed(2), 60, 55)
      result.writeString(atoms[i].temperature.toFixed(2), 66, 61)
      result.writeString(atoms[i].element.name, 78, 77)
      if (atoms[i].charge) {
        result.writeString(atoms[i].charge, 79, 80)
      }
    }
  }

  _extractCOMPND(result) {
    if (!this._source._molecules) {
      return
    }
    const molecules = this._source._molecules
    result.newTag('COMPND', true)

    for (let i = 0; i < molecules.length; i++) {
      const chains = this._getMoleculeChains(molecules[i])
      result.newString()
      result.writeString(`MOL_ID: ${molecules[i].index};`, 11, 80)
      result.newString()
      result.writeString(`MOLECULE: ${molecules[i].name};`, 11, 80)
      result.newString()
      result.writeString('CHAIN: ', 11, 18)
      const chainsString = `${chains.join(', ')};`
      result.writeEntireString(chainsString, 81)
    }
  }

  _extractREMARK(result) {
    this._Remark290(result)
    this._Remark350(result)
  }

  _Remark290(result) {
    if (!this._source.symmetry) {
      return
    }

    if (this._source.symmetry.length !== 0) {
      const matrices = this._source.symmetry
      result.newTag('REMARK', 290)
      result.newString()
      result.newString()
      result.writeEntireString(this._stringForRemark290)
      result.writeMatrices(matrices, 'SMTRY')
      result.newString()
      result.newString()
      result.writeString('REMARK: NULL', 11, 80)
    }
  }

  _Remark350(result) {
    if (!this._source.units) {
      return
    }
    const { units } = this._source
    let biomolIndx = 0

    result.newTag('REMARK', 350)
    result.newString()
    result.newString()
    result.writeEntireString(this._stringForRemark350)

    const assemblies = units.filter((unit) => unit instanceof Assembly)

    for (let i = 0; i < assemblies.length; i++) {
      result.newString()
      result.newString()
      biomolIndx++
      result.writeString(`BIOMOLECULE: ${biomolIndx}`, 11, 80)
      const chains = assemblies[i].chains.join(', ')
      result.newString()
      result.writeString('APPLY THE FOLLOWING TO CHAINS: ')
      result.writeEntireString(chains, 69, {
        tag: 'AND CHAINS: ',
        begin: 31,
        end: 42
      })

      const { matrices } = assemblies[i]
      result.writeMatrices(matrices, 'BIOMT')
    }
  }

  _getMoleculeChains(molecule) {
    function getChainName(residue) {
      return residue._chain._name
    }
    const chainNames = molecule.residues.map(getChainName)
    return chainNames.filter((item, pos) => chainNames.indexOf(item) === pos)
  }
}

PDBExporter.formats = ['pdb']
PDBExporter.SourceClass = Complex