NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/writer/pdb-writer.js

/**
 * @file Pdb Writer
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { defaults, ensureArray } from '../utils.js'
import Writer from './writer.js'

import { sprintf } from '../../lib/sprintf.es6.js'

// http://www.wwpdb.org/documentation/file-format

// Sample PDB line, the coords X,Y,Z are fields 5,6,7 on each line.
// ATOM      1  N   ARG     1      29.292  13.212 -12.751  1.00 33.78      1BPT 108

const AtomFormat =
  'ATOM  %5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s'

const HetatmFormat =
  'HETATM%5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s'

/**
 * Create a PDB file from a Structure object
 */
class PdbWriter extends Writer {
  /**
   * @param  {Structure} structure - the structure object
   * @param  {Object} params - parameters]
   */
  constructor (structure, params) {
    const p = Object.assign({}, params)

    super()

    this.renumberSerial = defaults(p.renumberSerial, true)
    this.remarks = ensureArray(defaults(p.remarks, []))

    this.structure = structure
    this._records = []
  }

  get mimeType () { return 'text/plain' }
  get defaultName () { return 'structure' }
  get defaultExt () { return 'pdb' }

  _writeRecords () {
    this._records.length = 0

    this._writeTitle()
    this._writeRemarks()
    this._writeAtoms()
  }

  _writeTitle () {
    // FIXME multiline if title line longer than 80 chars
    this._records.push(sprintf('TITLE %-74s', this.structure.name))
  }

  _writeRemarks () {
    this.remarks.forEach(str => {
      this._records.push(sprintf('REMARK %-73s', str))
    })

    if (this.structure.trajectory) {
      this._records.push(sprintf(
        'REMARK %-73s',
        "Trajectory '" + this.structure.trajectory.name + "'"
      ))
      this._records.push(sprintf(
        'REMARK %-73s',
        'Frame ' + this.structure.trajectory.frame + ''
      ))
    }
  }

  _writeAtoms () {
    let ia = 1
    let im = 1

    this.structure.eachModel(m => {
      this._records.push(sprintf('MODEL %-74d', im++))

      m.eachAtom(a => {
        const formatString = a.hetero ? HetatmFormat : AtomFormat
        const serial = this.renumberSerial ? ia : a.serial

        // Alignment of one-letter atom name such as C starts at column 14,
        // while two-letter atom name such as FE starts at column 13.
        let atomname = a.atomname
        if (atomname.length === 1) atomname = ' ' + atomname

        this._records.push(sprintf(
          formatString,

          serial,
          atomname,
          a.resname,
          defaults(a.chainname, ' '),
          a.resno,
          a.x, a.y, a.z,
          defaults(a.occurence, 1.0),
          defaults(a.bfactor, 0.0),
          defaults(a.segid, ''),
          defaults(a.element, '')
        ))
        ia += 1
      })

      this._records.push(sprintf('%-80s', 'ENDMDL'))
      im += 1
    })

    this._records.push(sprintf('%-80s', 'END'))
  }

  getString () {
    console.warn('PdbWriter.getString() is deprecated, use .getData instead')
    return this.getData()
  }

  /**
   * Get string containing the PDB file data
   * @return {String} PDB file
   */
  getData () {
    this._writeRecords()
    return this._records.join('\n')
  }
}

export default PdbWriter