diff --git a/package-lock.json b/package-lock.json index 1ce79bb2dc8d4d8557caa3e2f73f46de4c4992d2..445a602d57c899a8e98f013ba74f05382363076a 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index d6ddc61c460c78fca42b1210eedad59b287efd88..3185d9554462f5b524b2e682a7c2708e9e305a68 100644 --- a/package.json +++ b/package.json @@ -24,15 +24,20 @@ "transform": { "\\.ts$": "<rootDir>/node_modules/ts-jest/preprocessor.js" }, - "moduleDirectories": ["node_modules", "build/node_modules"], + "moduleDirectories": [ + "node_modules", + "build/node_modules" + ], "testRegex": "\\.spec\\.ts$" }, "author": "", "license": "MIT", "devDependencies": { "@types/benchmark": "^1.0.30", + "@types/express": "^4.0.39", "@types/jest": "^21.1.5", "@types/node": "^8.0.47", + "@types/node-fetch": "^1.6.7", "benchmark": "^2.1.4", "download-cli": "^1.0.5", "jest": "^21.2.1", @@ -45,8 +50,11 @@ "ts-jest": "^21.1.4", "tslint": "^5.8.0", "typescript": "^2.6.1", - "uglify-js": "^3.1.6", + "uglify-js": "^3.1.7", "util.promisify": "^1.0.0" }, - "dependencies": {} + "dependencies": { + "express": "^4.16.2", + "node-fetch": "^1.7.3" + } } diff --git a/src/apps/domain-annotation-server/mapping.ts b/src/apps/domain-annotation-server/mapping.ts new file mode 100644 index 0000000000000000000000000000000000000000..4489cb959ff608efb62c99c89081cdf1fccc1f05 --- /dev/null +++ b/src/apps/domain-annotation-server/mapping.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Table } from 'mol-base/collections/database' +import { CIFEncoder, create as createEncoder } from 'mol-io/writer/cif' +import * as S from './schemas' +import { getCategoryInstanceProvider } from './utils' + +export default function create(allData: any) { + const mols = Object.keys(allData); + if (!mols.length) return '#'; + + const data = allData[mols[0]]; + + const enc = createEncoder(); + enc.startDataBlock(mols[0]); + + for (const cat of Object.keys(S.categories)) { + writeDomain(enc, getDomain(cat, (S.categories as any)[cat], data)); + } + return enc.getData(); +} + +interface DomainAnnotation { + name: string, + domains: Table<any>, + mappings: Table<S.mapping> +} +type MappingRow = Table.Row<S.mapping>; + +function writeDomain(enc: CIFEncoder<any>, domain: DomainAnnotation | undefined) { + if (!domain) return; + enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_annotation`, domain.domains)); + enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_mapping`, domain.mappings)); +} + +function getMappings(startId: number, group_id: number, mappings: any): MappingRow[] { + const rows: MappingRow[] = []; + + const n = (v: any) => v === null ? void 0 : v; + + for (const entry of mappings) { + if (entry.start && entry.end) { + rows.push({ + id: startId++, + group_id, + label_entity_id: '' + entry.entity_id, + label_asym_id: entry.struct_asym_id, + auth_asym_id: entry.chain_id, + beg_label_seq_id: n(entry.start.residue_number), + beg_auth_seq_id: n(entry.start.author_residue_number), + pdbx_beg_PDB_ins_code: entry.start.author_insertion_code, + end_label_seq_id: n(entry.end.residue_number), + end_auth_seq_id: n(entry.end.author_residue_number), + pdbx_end_PDB_ins_code: entry.end.author_insertion_code + }); + } else { + rows.push({ + id: startId++, + group_id, + label_entity_id: '' + entry.entity_id, + label_asym_id: entry.struct_asym_id, + auth_asym_id: entry.chain_id + } as any); + } + } + return rows; +} + +function getDomainInfo(id: string, mapping_group_id: number, data: any, schema: any) { + const props = Object.create(null); + for (const k of Object.keys(schema)) props[k] = data[k]; + return { id, mapping_group_id, identifier: data.identifier, ...props }; +} + +function getDomain(name: string, schema: any, allData: any) { + if (!allData[name]) return void 0; + + const data = allData[name]; + + const domains: any[] = []; + const mappings: MappingRow[] = []; + + let mappingSerialId = 1, mapping_group_id = 1; + + for (const id of Object.keys(data)) { + const domain = data[id]; + domains.push(getDomainInfo(id, mapping_group_id, domain, schema)); + mappings.push(...getMappings(mappingSerialId, mapping_group_id, domain.mappings)); + mappingSerialId = mappings.length + 1; + mapping_group_id++; + } + + return domains.length > 0 ? { + name, + domains: Table.ofRows({ ...S.Base, ...schema }, domains), + mappings: Table.ofRows(S.mapping, mappings) + } : void 0; +} \ No newline at end of file diff --git a/src/apps/domain-annotation-server/schemas.ts b/src/apps/domain-annotation-server/schemas.ts new file mode 100644 index 0000000000000000000000000000000000000000..370bf1786499af3c02185619c4acbe5052934d37 --- /dev/null +++ b/src/apps/domain-annotation-server/schemas.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Column } from 'mol-base/collections/database' + +import Type = Column.Type + +export const Base = { + id: Type.str, + identifier: Type.str, + mapping_group_id: Type.int +} +export type Base = typeof Base + +export const mapping = { + id: Type.int, + group_id: Type.int, + + label_entity_id: Type.str, + label_asym_id: Type.str, + auth_asym_id: Type.str, + + beg_label_seq_id: Type.int, + beg_auth_seq_id: Type.int, + pdbx_beg_PDB_ins_code: Type.str, + + end_label_seq_id: Type.int, + end_auth_seq_id: Type.int, + pdbx_end_PDB_ins_code: Type.str +} +export type mapping = typeof mapping + +export const Pfam = { + description: Type.str +} +export type Pfam = typeof Pfam + +export const InterPro = { + name: Type.str +} +export type InterPro = typeof InterPro + +export const CATH = { + name: Type.str, + homology: Type.str, + architecture: Type.str, + identifier: Type.str, + class: Type.str, + topology: Type.str, +} +export type CATH = typeof CATH + +export const EC = { + accepted_name: Type.str, + reaction: Type.str, + systematic_name: Type.str +} +export type EC = typeof EC + +export const UniProt = { + name: Type.str +} +export type UniProt = typeof UniProt + +export const SCOP = { + sccs: Type.str, + description: Type.str +} +export type SCOP = typeof SCOP + +export const GO = { + category: Type.str, + definition: Type.str, + name: Type.str +} +export type GO = typeof GO + +export const categories = { + Pfam, + InterPro, + CATH, + EC, + UniProt, + SCOP, + GO +} \ No newline at end of file diff --git a/src/apps/domain-annotation-server/server.ts b/src/apps/domain-annotation-server/server.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d4f192cad0713c96b559e427535986532c8dc54 --- /dev/null +++ b/src/apps/domain-annotation-server/server.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as express from 'express' +import fetch from 'node-fetch' +import createMapping from './mapping' + +async function getMappings(id: string) { + const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`); + const json = await data.json(); + return createMapping(json); +}; + + +let PORT = process.env.port || 1338; + +const app = express(); + +const PREFIX = '/' + +app.get(`${PREFIX}/:id`, async (req, res) => { + try { + console.log('Requesting ' + req.params.id); + const mapping = await getMappings(req.params.id); + res.writeHead(200, { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'X-Requested-With' + }); + res.end(mapping); + } catch { + console.log('Failed ' + req.params.id); + res.writeHead(404, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'X-Requested-With' }); + res.end(); + } +}); + +app.get(`${PREFIX}`, (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Usage: /pdb_id, e.g. /1tqn'); +}) + +app.listen(PORT); + +console.log('Running on port ' + PORT); \ No newline at end of file diff --git a/src/apps/domain-annotation-server/test.ts b/src/apps/domain-annotation-server/test.ts new file mode 100644 index 0000000000000000000000000000000000000000..66911dec6ce7484f2bd24bc3784b126cbe974259 --- /dev/null +++ b/src/apps/domain-annotation-server/test.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import fetch from 'node-fetch' +import createMapping from './mapping' + +(async function () { + const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true'); + const json = await data.json(); + console.log(createMapping(json)); +}()); \ No newline at end of file diff --git a/src/apps/domain-annotation-server/utils.ts b/src/apps/domain-annotation-server/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb5c6badddce091441def0b26f8ab6b8c552f247 --- /dev/null +++ b/src/apps/domain-annotation-server/utils.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Table } from 'mol-base/collections/database' +import Iterator from 'mol-base/collections/iterator' +import * as Encoder from 'mol-io/writer/cif' + +function columnValue(k: string) { + return (i: number, d: any) => d[k].value(i); +} + +function columnValueKind(k: string) { + return (i: number, d: any) => d[k].valueKind(i); +} + +function ofSchema(schema: Table.Schema) { + const fields: Encoder.FieldDefinition[] = []; + for (const k of Object.keys(schema)) { + const t = schema[k]; + // TODO: matrix/vector/support + const type = t.kind === 'str' ? Encoder.FieldType.Str : t.kind === 'int' ? Encoder.FieldType.Int : Encoder.FieldType.Float; + fields.push({ name: k, type, value: columnValue(k), valueKind: columnValueKind(k) }) + } + return fields; +} + +function ofTable<S extends Table.Schema>(name: string, table: Table<S>): Encoder.CategoryDefinition<number> { + return { name, fields: ofSchema(table._schema) } +} + +export function getCategoryInstanceProvider(name: string, table: Table<any>): Encoder.CategoryProvider { + return () => { + return { + data: table, + definition: ofTable(name, table), + keys: () => Iterator.Range(0, table._rowCount - 1), + rowCount: table._rowCount + }; + } +} diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index 64a18622ff509c86fe1ae394c239b9bf7915f898..f32bec34bf5506c7efff06723630e89980e5bea7 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/src/mol-io/writer/cif/encoder.ts @@ -57,7 +57,7 @@ export interface CategoryProvider { (ctx: any): CategoryInstance } -export interface CIFEncoder<T, Context> extends Encoder<T> { +export interface CIFEncoder<T = string | Uint8Array, Context = any> extends Encoder<T> { startDataBlock(header: string): void, writeCategory(category: CategoryProvider, contexts?: Context[]): void, getData(): T