private SpatialBinCollection doSpatialBinning(BinningProductFilter productFilter)
     throws IOException {
   SpatialBinCollector spatialBinCollector =
       new GeneralSpatialBinCollector(binningContext.getPlanetaryGrid().getNumBins());
   final SpatialBinner spatialBinner = new SpatialBinner(binningContext, spatialBinCollector);
   if (sourceProducts != null) {
     for (Product sourceProduct : sourceProducts) {
       if (productFilter.accept(sourceProduct)) {
         processSource(sourceProduct, spatialBinner);
       } else {
         getLogger().warning("Filtered out product '" + sourceProduct.getFileLocation() + "'");
         getLogger().warning("              reason: " + productFilter.getReason());
       }
     }
   }
   if (sourceProductPaths != null) {
     SortedSet<File> fileSet = new TreeSet<>();
     for (String filePattern : sourceProductPaths) {
       WildcardMatcher.glob(filePattern, fileSet);
     }
     if (fileSet.isEmpty()) {
       getLogger().warning("The given source file patterns did not match any files");
     }
     for (File file : fileSet) {
       Product sourceProduct = null;
       try {
         if (sourceProductFormat != null) {
           sourceProduct = ProductIO.readProduct(file, sourceProductFormat);
         } else {
           sourceProduct = ProductIO.readProduct(file);
         }
       } catch (Exception e) {
         String msgPattern = "Failed to read file '%s'. %s: %s";
         getLogger()
             .severe(
                 String.format(msgPattern, file, e.getClass().getSimpleName(), e.getMessage()));
       }
       if (sourceProduct != null) {
         try {
           if (productFilter.accept(sourceProduct)) {
             processSource(sourceProduct, spatialBinner);
           } else {
             getLogger().warning("Filtered out product '" + sourceProduct.getFileLocation() + "'");
             getLogger().warning("              reason: " + productFilter.getReason());
           }
         } finally {
           sourceProduct.dispose();
         }
       } else {
         String msgPattern = "Failed to read file '%s' (not a data product or reader missing)";
         getLogger().severe(String.format(msgPattern, file));
       }
     }
   }
   spatialBinCollector.consumingCompleted();
   return spatialBinCollector.getSpatialBinCollection();
 }
  private void writeOutput(
      List<TemporalBin> temporalBins, ProductData.UTC startTime, ProductData.UTC stopTime)
      throws Exception {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    initMetadataProperties();

    if (outputBinnedData) {
      try {
        writeNetCDFBinFile(temporalBins, startTime, stopTime);
      } catch (Exception e) {
        getLogger()
            .log(Level.SEVERE, String.format("Failed to write binned data: %s", e.getMessage()), e);
      }
    }

    if (outputTargetProduct) {
      getLogger()
          .info(String.format("Writing mapped product '%s'...", formatterConfig.getOutputFile()));
      final MetadataElement processingGraphMetadata = getProcessingGraphMetadata();
      Formatter.format(
          binningContext.getPlanetaryGrid(),
          getTemporalBinSource(temporalBins),
          binningContext.getBinManager().getResultFeatureNames(),
          formatterConfig,
          region,
          startTime,
          stopTime,
          processingGraphMetadata);
      stopWatch.stop();

      String msgPattern = "Writing mapped product '%s' done, took %s";
      getLogger().info(String.format(msgPattern, formatterConfig.getOutputFile(), stopWatch));

      if (outputType.equalsIgnoreCase("Product")) {
        final File writtenProductFile = new File(outputFile);
        String format = Formatter.getOutputFormat(formatterConfig, writtenProductFile);
        writtenProduct = ProductIO.readProduct(writtenProductFile, format);
        this.targetProduct = copyProduct(writtenProduct);
      } else {
        this.targetProduct = new Product("Dummy", "t", 10, 10);
      }
    } else {
      this.targetProduct = new Product("Dummy", "t", 10, 10);
    }
  }