static PyObject *center(PyObject *self, PyObject *args) {
  int molid, frame;
  PyObject *selected;
  PyObject *weightobj = NULL;
  AtomSel *sel;
  // parse arguments
  if (!PyArg_ParseTuple(args, (char *)"iiO!|O",
        &molid, &frame, &PyTuple_Type, &selected, &weightobj))
    return NULL;
  VMDApp *app = get_vmdapp();
  // get selection
  if (!(sel = sel_from_py(molid, frame, selected, app)))
    return NULL;
  // get weight
  float *weight = parse_weight(sel, weightobj);
  if (!weight) return NULL;
  float cen[3];
  // compute center
  int ret_val = measure_center(sel, sel->coordinates(app->moleculeList),
      weight, cen);
  delete [] weight;
  delete sel;
  if (ret_val < 0) {
    PyErr_SetString(PyExc_ValueError, measure_error(ret_val));
    return NULL;
  }
  // return as (x, y, z)
  PyObject *cenobj = PyTuple_New(3);
  for (int i=0; i<3; i++)
    PyTuple_SET_ITEM(cenobj, i, PyFloat_FromDouble(cen[i]));
  return cenobj;
}
static PyObject *py_rmsd(PyObject *self, PyObject *args) {
  int mol1, frame1, mol2, frame2;
  PyObject *selected1, *selected2, *weightobj = NULL;
  if (!PyArg_ParseTuple(args, (char *)"iiO!iiO!O:atomselection.rmsd",
        &mol1, &frame1, &PyTuple_Type, &selected1,
        &mol2, &frame2, &PyTuple_Type, &selected2,
				&weightobj))
    return NULL;
  VMDApp *app = get_vmdapp();
  AtomSel *sel1 = sel_from_py(mol1, frame1, selected1, app);
  AtomSel *sel2 = sel_from_py(mol2, frame2, selected2, app);
  if (!sel1 || !sel2) {
    delete sel1;
    delete sel2;
    return NULL;
  }
  const Timestep *ts1 =app->moleculeList->mol_from_id(mol1)->get_frame(frame1); 
  const Timestep *ts2 =app->moleculeList->mol_from_id(mol2)->get_frame(frame2); 
  if (!ts1 || !ts2) {
    PyErr_SetString(PyExc_ValueError, "No coordinates in selection");
    delete sel1;
    delete sel2;
    return NULL;
  }
  float *weight = parse_weight(sel1, weightobj);
  if (!weight) {
    delete sel1;
    delete sel2;
    return NULL;
  }
  float rmsd;
  int rc = measure_rmsd(sel1, sel2, sel1->selected, ts1->pos, ts2->pos,
      weight, &rmsd);
  delete sel1;
  delete sel2;
  delete [] weight;
  if (rc < 0) {
    PyErr_SetString(PyExc_ValueError, measure_error(rc));
    return NULL;
  }
  return PyFloat_FromDouble(rmsd);
}
static PyObject *py_align(PyObject *self, PyObject *args) {
  int selmol, selframe, refmol, refframe, movemol, moveframe;
  PyObject *selobj, *refobj, *moveobj, *weightobj = NULL;
  if (!PyArg_ParseTuple(args, (char *)"iiO!iiO!iiO!O:atomselection.align",
        &selmol, &selframe, &PyTuple_Type, &selobj,
        &refmol, &refframe, &PyTuple_Type, &refobj,
        &movemol, &moveframe, &PyTuple_Type, &moveobj,
        &weightobj))
    return NULL;

  // check if movemol is -1.  If so, use the sel molecule and timestep instead
  if (movemol == -1) {
    movemol = selmol;
    moveobj = NULL;
  }
  VMDApp *app = get_vmdapp();
  AtomSel *sel=NULL, *ref=NULL, *move=NULL;
  if (!(sel = sel_from_py(selmol, selframe, selobj, app)) ||
      !(ref = sel_from_py(refmol, refframe, refobj, app)) ||
      !(move = sel_from_py(movemol, moveframe, moveobj, app))) {
    delete sel;
    delete ref;
    delete move;
    return NULL;
  }
  const float *selts, *refts;
  float *movets;
  if (!(selts = sel->coordinates(app->moleculeList)) ||
      !(refts = ref->coordinates(app->moleculeList)) || 
      !(movets = move->coordinates(app->moleculeList))) {
    delete sel;
    delete ref;
    delete move;
    PyErr_SetString(PyExc_ValueError, "No coordinates in selection");
    return NULL;
  }
  float *weight = parse_weight(sel, weightobj);
  if (!weight) {
    delete sel;
    delete ref;
    delete move;
    return NULL;
  }
  // Find the matrix that aligns sel with ref.  Apply the transformation to
  // the atoms in move.
  // XXX need to add support for the "order" parameter as in Tcl.
  Matrix4 mat;
  int rc = measure_fit(sel, ref, selts, refts, weight, NULL, &mat);
  delete [] weight;
  delete sel;
  delete ref;
  if (rc < 0) {
    delete move;
    PyErr_SetString(PyExc_ValueError, (char *)measure_error(rc));
    return NULL;
  }
  for (int i=0; i<move->num_atoms; i++) {
    if (move->on[i]) {
      float *pos = movets+3*i;
      mat.multpoint3d(pos, pos);
    }
  }
  Molecule *mol = app->moleculeList->mol_from_id(move->molid());
  mol->force_recalc(DrawMolItem::MOL_REGEN);
  delete move;
  Py_INCREF(Py_None);
  return Py_None;
}
void ProcessWayTags(TagList *tags,int64_t way_id,int mode)
{
 Way way={0};
 int oneway=0,area=0;
 int roundabout=0,lanes=0;
 char *name=NULL,*ref=NULL,*refname=NULL;
 way_t id;
 int i;

 /* Convert id */

 id=(way_t)way_id;
 logassert((int64_t)id==way_id,"Way ID too large (change way_t to 64-bits?)"); /* check way id can be stored in way_t data type. */

 /* Delete */

 if(mode==MODE_DELETE || mode==MODE_MODIFY)
   {
    way.type=WAY_DELETED;

    AppendWayList(ways,id,&way,way_nodes,way_nnodes,"");
   }

 if(mode==MODE_DELETE)
    return;

 /* Sanity check */

 if(way_nnodes==0)
   {
    logerror("Way %"Pway_t" has no nodes.\n",logerror_way(id));
    return;
   }

 if(way_nnodes==1)
   {
    logerror_node(way_nodes[0]); /* Extra logerror information since way isn't stored */
    logerror("Way %"Pway_t" has only one node.\n",logerror_way(id));
    return;
   }

 /* Parse the tags - just look for highway */

 for(i=0;i<tags->ntags;i++)
   {
    char *k=tags->k[i];
    char *v=tags->v[i];

    if(!strcmp(k,"highway"))
      {
       way.type=HighwayType(v);

       if(way.type==Highway_None)
          logerror("Way %"Pway_t" has an unrecognised highway type '%s' (after tagging rules); ignoring it.\n",logerror_way(id),v);

       break;
      }
   }

 /* Don't continue if this is not a highway (bypass error logging) */

 if(way.type==Highway_None)
    return;

 /* Parse the tags - look for the others */

 for(i=0;i<tags->ntags;i++)
   {
    int recognised=0;
    char *k=tags->k[i];
    char *v=tags->v[i];

    switch(*k)
      {
      case 'a':
       if(!strcmp(k,"area"))
         {
          if(ISTRUE(v))
             area=1;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'area' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'b':
       if(!strcmp(k,"bicycle"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Bicycle;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'bicycle' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"bicycleroute"))
         {
          if(ISTRUE(v))
             way.props|=Properties_BicycleRoute;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'bicycleroute' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"bridge"))
         {
          if(ISTRUE(v))
             way.props|=Properties_Bridge;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'bridge' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

	  case 'c':
       if(!strcmp(k,"cycleway"))
         {
          if(!strcmp(v,"opposite_lane"))
             way.props|=Properties_DoubleSens;
           recognised=1; break;
         }
	    break;
	   
      case 'f':
       if(!strcmp(k,"foot"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Foot;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'foot' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"footroute"))
         {
          if(ISTRUE(v))
             way.props|=Properties_FootRoute;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'footroute' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'g':
       if(!strcmp(k,"goods"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Goods;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'goods' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'h':
       if(!strcmp(k,"highway"))
         {recognised=1; break;}

       if(!strcmp(k,"horse"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Horse;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'horse' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"hgv"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_HGV;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'hgv' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

	  case 'i':
       if(!strcmp(k,"incline"))
         {
/* logerror("Way %"Pway_t" has an 'incline' = '%s' \n",logerror_way(id),v); */
         way.incline=pourcent_to_incline(parse_incline(id,k,v));
          recognised=1; break;
		 }
	    break;


      case 'l':
       if(!strcmp(k,"lanes"))
         {
          int en=0;
          float lanesf;
          if(sscanf(v,"%f%n",&lanesf,&en)==1 && en && !v[en])
             lanes=(int)lanesf;
          else
             logerror("Way %"Pway_t" has an unrecognised tag 'lanes' = '%s' (after tagging rules); ignoring it.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'm':
       if(!strncmp(k,"max",3))
         {
          if(!strcmp(k+3,"speed"))
            {
             way.speed=kph_to_speed(parse_speed(id,k,v));
             recognised=1; break;
            }

          if(!strcmp(k+3,"weight"))
            {
             way.weight=tonnes_to_weight(parse_weight(id,k,v));
             recognised=1; break;
            }

          if(!strcmp(k+3,"height"))
            {
             way.height=metres_to_height(parse_length(id,k,v));
             recognised=1; break;
            }

          if(!strcmp(k+3,"width"))
            {
             way.width=metres_to_height(parse_length(id,k,v));
             recognised=1; break;
            }

          if(!strcmp(k+3,"length"))
            {
             way.length=metres_to_height(parse_length(id,k,v));
             recognised=1; break;
            }
         }

       if(!strcmp(k,"moped"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Moped;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'moped' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"motorcycle"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Motorcycle;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'motorcycle' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"motorcar"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Motorcar;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'motorcar' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"multilane"))
         {
          if(ISTRUE(v))
             way.props|=Properties_Multilane;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'multilane' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'n':
       if(!strcmp(k,"name"))
         {
          name=v;
          recognised=1; break;
         }

       break;

      case 'o':
       if(!strcmp(k,"oneway"))
         {
          if(ISTRUE(v))
             oneway=1;
          else if(!strcmp(v,"-1"))
             oneway=-1;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'oneway' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'p':
       if(!strcmp(k,"paved"))
         {
          if(ISTRUE(v))
             way.props|=Properties_Paved;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'paved' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       if(!strcmp(k,"psv"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_PSV;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'psv' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'r':
       if(!strcmp(k,"ref"))
         {
          ref=v;
          recognised=1; break;
         }

       if(!strcmp(k,"roundabout"))
         {
          if(ISTRUE(v))
             roundabout=1;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'roundabout' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 't':
       if(!strcmp(k,"tunnel"))
         {
          if(ISTRUE(v))
             way.props|=Properties_Tunnel;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'tunnel' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      case 'w':
       if(!strcmp(k,"wheelchair"))
         {
          if(ISTRUE(v))
             way.allow|=Transports_Wheelchair;
          else if(!ISFALSE(v))
             logerror("Way %"Pway_t" has an unrecognised tag 'wheelchair' = '%s' (after tagging rules); using 'no'.\n",logerror_way(id),v);
          recognised=1; break;
         }

       break;

      default:
       break;
      }

    if(!recognised)
       logerror("Way %"Pway_t" has an unrecognised tag '%s' = '%s' (after tagging rules); ignoring it.\n",logerror_way(id),k,v);
   }

 /* Create the way */

 if(area && oneway)
   {
    logerror("Way %"Pway_t" is an area and oneway; ignoring area tagging.\n",logerror_way(id));
    area=0;
   }

 if(!way.allow)
    return;

 if(oneway)
   {
    way.type|=Highway_OneWay;

    if(oneway==-1)
       for(i=0;i<way_nnodes/2;i++)
         {
          node_t temp;

          temp=way_nodes[i];
          way_nodes[i]=way_nodes[way_nnodes-i-1];
          way_nodes[way_nnodes-i-1]=temp;
         }
   }

 if(roundabout)
    way.type|=Highway_Roundabout;

 if(area)
   {
    way.type|=Highway_Area;

    if(way_nodes[0]!=way_nodes[way_nnodes-1])
       logerror("Way %"Pway_t" is an area but not closed.\n",logerror_way(id));
   }

 if(lanes)
   {
    if(oneway || (lanes/2)>1)
       way.props|=Properties_Multilane;

    if(oneway && lanes==1)
       way.props&=~Properties_Multilane;
   }

 if(ref && name)
   {
    refname=(char*)malloc(strlen(ref)+strlen(name)+4);
    sprintf(refname,"%s (%s)",name,ref);
   }
 else if(ref && !name)
    refname=ref;
 else if(!ref && name)
    refname=name;
 else /* if(!ref && !name) */
    refname="";

 AppendWayList(ways,id,&way,way_nodes,way_nnodes,refname);

 if(ref && name)
    free(refname);
}