001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.structure.quaternary;
022
023import org.biojava.nbio.structure.xtal.CrystalCell;
024import org.biojava.nbio.structure.xtal.CrystalTransform;
025import org.biojava.nbio.core.util.PrettyXMLWriter;
026import org.w3c.dom.Document;
027import org.w3c.dom.NamedNodeMap;
028import org.w3c.dom.Node;
029import org.w3c.dom.NodeList;
030import org.xml.sax.InputSource;
031import org.xml.sax.SAXException;
032
033import javax.vecmath.Matrix4d;
034import javax.vecmath.Point3d;
035import javax.xml.parsers.DocumentBuilder;
036import javax.xml.parsers.DocumentBuilderFactory;
037import javax.xml.parsers.ParserConfigurationException;
038import java.io.*;
039import java.util.ArrayList;
040import java.util.List;
041
042/**
043 * The transformation needed for generation of biological assemblies
044 * from the contents of a PDB/mmCIF file. It contains both the actual
045 * transformation (rotation+translation) and the chain identifier to
046 * which it should be applied.
047 *
048 * @author Peter Rose
049 * @author Andreas Prlic
050 * @author rickb
051 * @author Jose Duarte
052 * @see CrystalTransform
053 */
054public class BiologicalAssemblyTransformation implements Cloneable, Comparable<BiologicalAssemblyTransformation>, Serializable {
055
056        private static final long serialVersionUID = -6388503076022480391L;
057
058        private String id;
059        private String chainId;
060        private Matrix4d transformation;
061
062        /**
063         * Default Constructor
064         */
065        public BiologicalAssemblyTransformation() {
066                // we initialize to identity so that setting rotation and translation work properly
067                transformation = new Matrix4d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
068        }
069
070        /**
071         * Copy Constructor
072         *
073         * @param src
074         */
075        public BiologicalAssemblyTransformation(final BiologicalAssemblyTransformation src)
076        {
077                this.transformation = new Matrix4d(src.transformation);
078                this.id = src.getId();
079                this.chainId =  src.getChainId();
080        }
081
082
083        /**
084         * Sets the identifier for this biological assembly transformation. This is usually
085         * the model number used in the biological assembly files.
086         * @param id
087         */
088        public void setId(String id) {
089                this.id = id;
090        }
091
092        /**
093         * Returns the identifier for this biological assembly transformation.
094         * @return biological assembly transformation identifier
095         */
096        public String getId() {
097                return id;
098        }
099
100        /**
101         * Sets the chain identifier (asym id) that this transformation should be applied to.
102         * @param chainId
103         */
104        public void setChainId(String chainId) {
105                this.chainId = chainId;
106        }
107
108        /**
109         * Returns the chain identifier (asym id) that this transformation should be applied to.
110         * @return chain identifier
111         */
112        public String getChainId() {
113                return this.chainId;
114        }
115
116        /**
117         * Sets the transformation using a 4x4 transformation matrix
118         * @param transformation
119         */
120        public void setTransformationMatrix(Matrix4d transformation) {
121                this.transformation = transformation;
122        }
123
124        /**
125         * Return the transformation (both rotational and translational component) as a 4x4 transformation matrix.
126         * The transformation is in orthonormal (cartesian coordinates). If required to be converted to
127         * crystal coordinates then use {@link CrystalCell#transfToCrystal(Matrix4d)}
128         * Note that this is a reference to the variable, thus it remains linked to this object's transformation field.
129         * The user must deep copy it if need changing it.
130         * @return 4x4 transformation matrix
131         */
132        public Matrix4d getTransformationMatrix() {
133                return transformation;
134        }
135
136        public void setRotationMatrix(double[][] m) {
137                for (int i=0;i<3;i++) {
138                        for (int j=0;j<3;j++) {
139                                this.transformation.setElement(i, j, m[i][j]);
140                        }
141                }
142        }
143
144        public void setTranslation(double[] t) {
145                for (int i=0;i<3;i++) {
146                        this.transformation.setElement(i, 3, t[i]);
147                }
148        }
149
150        /**
151         * Applies the transformation to given point.
152         */
153        public void transformPoint(final double[] point) {
154                Point3d p = new Point3d(point[0],point[1],point[2]);
155                transformation.transform(p);
156                point[0] = p.x;
157                point[1] = p.y;
158                point[2] = p.z;
159        }
160
161        /**
162         * Returns the combination (product) of two biological assembly transformations.
163         * @param matrix1
164         * @param matrix2
165         * @return combined transformation
166         */
167        public static BiologicalAssemblyTransformation combine(BiologicalAssemblyTransformation matrix1, BiologicalAssemblyTransformation matrix2) {
168                Matrix4d transformation = new Matrix4d(matrix1.transformation);
169                transformation.mul(matrix2.transformation);
170                BiologicalAssemblyTransformation combined = new BiologicalAssemblyTransformation();
171                combined.setTransformationMatrix(transformation);
172                return combined;
173        }
174
175        /**
176         * Tells whether this transformation is in identity.
177         * @return
178         */
179        public boolean isIdentity() {
180                return transformation.epsilonEquals(new Matrix4d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1), 0.00000000001);
181        }
182
183        public String toXML() throws IOException{
184
185                StringWriter sw = new StringWriter();
186                PrintWriter writer = new PrintWriter(sw);
187
188                PrettyXMLWriter xml = new PrettyXMLWriter(new PrintWriter(writer));
189
190                toXML(xml);
191
192                xml.close();
193                writer.close();
194                sw.close();
195                return sw.toString();
196        }
197
198        public void toXML(PrettyXMLWriter xml) throws IOException{
199                xml.openTag("transformation");
200                xml.attribute("index",id);
201
202                xml.openTag("matrix");
203
204                        for ( int i = 0 ; i<3 ; i++){
205                                for ( int j = 0 ; j<3 ;j++){
206                                xml.attribute("m" +  (i+1) + (j+1), String.format("%.8f",transformation.getElement(i,j)));
207                        }
208                }
209                xml.closeTag("matrix");
210
211                xml.openTag("shift");
212                for ( int i = 0 ; i<3 ; i++) {
213                        xml.attribute("v"+(i+1),String.format("%.8f", transformation.getElement(i,3)));
214                }
215                xml.closeTag("shift");
216
217                xml.closeTag("transformation");
218
219        }
220
221        public static BiologicalAssemblyTransformation fromXML(String xml)
222                        throws SAXException,
223                        IOException,
224                        ParserConfigurationException{
225
226
227                List<BiologicalAssemblyTransformation> transformations = fromMultiXML(xml);
228
229                if ( transformations.size() > 0)
230                        return transformations.get(0);
231
232                else
233                        return null;
234        }
235
236        public static List<BiologicalAssemblyTransformation> fromMultiXML(String xml) throws ParserConfigurationException, SAXException, IOException{
237
238
239                List<BiologicalAssemblyTransformation> transformations = new ArrayList<BiologicalAssemblyTransformation>();
240
241                // read the XML of a string and returns a ModelTransformationmatrix
242                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
243                DocumentBuilder db = factory.newDocumentBuilder();
244
245                InputSource inStream = new InputSource();
246                inStream.setCharacterStream(new StringReader(xml));
247                Document doc = db.parse(inStream);
248
249                // normalize text representation
250                doc.getDocumentElement().normalize();;
251
252                NodeList listOfTransforms = doc.getElementsByTagName("transformation");
253
254                for(int pos=0; pos<listOfTransforms.getLength() ; pos++) {
255                        Node rootElement       = listOfTransforms.item(pos);
256
257                        BiologicalAssemblyTransformation max = new BiologicalAssemblyTransformation();
258
259                        max.id = getAttribute(rootElement,"index");
260                        max.chainId = getAttribute(rootElement,"chainName");
261
262                        NodeList listOfChildren = rootElement.getChildNodes();
263
264
265                        for(int i=0; i<listOfChildren.getLength() ; i++)
266                        {
267                                // and now the matrix ...
268                                Node block       = listOfChildren.item(i);
269
270                                // we only look at blocks.
271                                if ( block.getNodeName().equals("matrix"))
272                                        max.setRotationMatrix(getMatrixFromXML(block));
273
274                                if ( block.getNodeName().equals("shift"))
275                                        max.setTranslation(getVectorFromXML(block));
276
277                        }
278
279                        transformations.add(max);
280                }
281
282                return transformations;
283        }
284
285        private static double[] getVectorFromXML(Node block) {
286                double[] d = new double[3];
287                for ( int i = 0 ; i<3 ; i++){
288                        d[i] = Float.parseFloat(getAttribute(block, "v" + (i+1) ));
289                }
290                return d;
291        }
292
293        private static double[][] getMatrixFromXML(Node block) {
294                double[][] m  = new double[3][3];
295                for ( int i = 0 ; i<3 ; i++){
296                        for ( int j = 0 ; j<3 ;j++){
297                                // TODO check is this matrix is populated correctly
298                                String val = getAttribute(block, "m" + (j+1)+(i+1));
299                                m[j][i] = Double.parseDouble(val);
300                        }
301                }
302                return m;
303        }
304
305        private static String getAttribute(Node node, String attr){
306                if( ! node.hasAttributes())
307                        return null;
308
309                NamedNodeMap atts = node.getAttributes();
310
311                if ( atts == null)
312                        return null;
313
314                Node att = atts.getNamedItem(attr);
315                if ( att == null)
316                        return null;
317
318                String value = att.getTextContent();
319
320                return value;
321
322        }
323
324        /* (non-Javadoc)
325         * @see java.lang.Object#toString()
326         */
327        @Override
328        public String toString() {
329                return "BiologicalAssemblyTransformation [id=" + id + ", chainId="
330                                + chainId + ", rotation=" + rotMatrixToString(transformation) + ", translation="
331                                + translVecToString(transformation) + "]";
332        }
333
334        public static String rotMatrixToString(Matrix4d m) {
335                return String.format("(%5.2f %5.2f %5.2f, %5.2f %5.2f %5.2f, %5.2f %5.2f %5.2f)", m.m00, m.m01, m.m02,  m.m10, m.m11, m.m12,  m.m20, m.m21, m.m22);
336        }
337
338        public static String translVecToString(Matrix4d m) {
339                return String.format("(%5.2f %5.2f %5.2f)", m.m03, m.m13, m.m23);
340        }
341
342        @Override
343        public int compareTo(BiologicalAssemblyTransformation other) {
344                int comp = this.chainId.compareTo(other.chainId);
345                return comp == 0 ? this.id.compareTo(other.id) : comp;
346        }
347}