docs: def prepare_site_model(exposure_xml, sites_csv, vs30_csv,
                         z1pt0, z2pt5, vs30measured, grid_spacing=0,
                         assoc_distance=5, output='site_model.csv'):
      """
      Prepare a site_model.csv file from exposure xml files/site csv files,
      vs30 csv files and a grid spacing which can be 0 (meaning no grid).
      For each site the closest vs30 parameter is used. The command can also
      generate (on demand) the additional fields z1pt0, z2pt5 and vs30measured
      which may be needed by your hazard model, depending on the required GSIMs.
      """
      hdf5 = datastore.hdf5new()
      req_site_params = {'vs30'}
      fields = ['lon', 'lat', 'vs30']
      if z1pt0:
          req_site_params.add('z1pt0')
          fields.append('z1pt0')
      if z2pt5:
          req_site_params.add('z2pt5')
          fields.append('z2pt5')
      if vs30measured:
          req_site_params.add('vs30measured')
          fields.append('vs30measured')
      with performance.Monitor(hdf5.path, hdf5, measuremem=True) as mon:
          if exposure_xml:
              mesh, assets_by_site = Exposure.read(
                  exposure_xml, check_dupl=False).get_mesh_assets_by_site()
              mon.hdf5['assetcol'] = assetcol = site.SiteCollection.from_points(
                  mesh.lons, mesh.lats, req_site_params=req_site_params)
              if grid_spacing:
                  grid = mesh.get_convex_hull().dilate(
                      grid_spacing).discretize(grid_spacing)
                  haz_sitecol = site.SiteCollection.from_points(
                      grid.lons, grid.lats, req_site_params=req_site_params)
                  logging.info(
                      'Associating exposure grid with %d locations to %d '
                      'exposure sites', len(haz_sitecol), len(assets_by_site))
                  haz_sitecol, assets_by, discarded = assoc(
                      assets_by_site, haz_sitecol,
                      grid_spacing * SQRT2, 'filter')
                  if len(discarded):
                      logging.info('Discarded %d sites with assets '
                                   '[use oq plot_assets]', len(discarded))
                      mon.hdf5['discarded'] = numpy.array(discarded)
                  haz_sitecol.make_complete()
              else:
                  haz_sitecol = assetcol
                  discarded = []
          elif sites_csv:
              lons, lats = [], []
              for fname in sites_csv:
                  with open(fname) as csv:
                      for line in csv:
                          if line.startswith('lon,lat'):  # possible header
                              continue
                          lon, lat = line.split(',')[:2]
                          lons.append(valid.longitude(lon))
                          lats.append(valid.latitude(lat))
              haz_sitecol = site.SiteCollection.from_points(
                  lons, lats, req_site_params=req_site_params)
              if grid_spacing:
                  grid = mesh.get_convex_hull().dilate(
                      grid_spacing).discretize(grid_spacing)
                  haz_sitecol = site.SiteCollection.from_points(
                      grid.lons, grid.lats, req_site_params=req_site_params)
          else:
              raise RuntimeError('Missing exposures or missing sites')
          vs30orig = read_vs30(vs30_csv)
          logging.info('Associating %d hazard sites to %d site parameters',
                       len(haz_sitecol), len(vs30orig))
          sitecol, vs30, _ = assoc(
              vs30orig, haz_sitecol, assoc_distance, 'warn')
          sitecol.array['vs30'] = vs30['vs30']
          if z1pt0:
              sitecol.array['z1pt0'] = calculate_z1pt0(vs30['vs30'])
          if z2pt5:
              sitecol.array['z2pt5'] = calculate_z2pt5_ngaw2(vs30['vs30'])
          if vs30measured:
              sitecol.array['vs30measured'] = False  # it is inferred
          mon.hdf5['sitecol'] = sitecol
          write_csv(output, sitecol.array[fields])
      logging.info('Saved %d rows in %s' % (len(sitecol), output))
      logging.info(mon)
      return sitecol