/**
   * Compute the facade index for given osm tags, polygon and height.
   *
   * @param tags
   * @param polygon
   * @param height
   * @return Integer the facade index.
   */
  public Integer computeFacadeIndex(OsmPolygon polygon) {
    Integer result = null;
    // first, do we are on a residential object?
    // yes if there is residential or house tag
    // or if surface of the polygon is under the max surface for a
    // residential house
    // and height is under max residential height
    if ((OsmUtils.isValueinTags("residential", polygon.getTags())
            || OsmUtils.isValueinTags("house", polygon.getTags())
            || polygon.getArea() * 10000000 < ASSERTION_RESIDENTIAL_MAX_AREA)
        && polygon.getHeight() < XplaneOptionsHelper.getOptions().getResidentialMax()) {

      result = computeResidentialFacadeIndex(polygon);
    }

    // do we are on a building object?
    // yes if there is industrial or Commercial tag
    // or if surface of the polygon is above the max surface for a
    // residential house
    // and height is above max residential height
    else {
      if (OsmUtils.isValueinTags("industrial", polygon.getTags())
          || OsmUtils.isValueinTags("Commercial", polygon.getTags())
          || polygon.getArea() * 10000000 > ASSERTION_RESIDENTIAL_MAX_AREA
          || polygon.getHeight() > XplaneOptionsHelper.getOptions().getResidentialMax()) {

        result = computeBuildingFacadeIndex(polygon);
      }
    }
    return result;
  }
  @Override
  public void complete() {

    // if smart exclusions enabled and tile is not empty, send them to
    // writer
    if (!StatsHelper.isTileEmpty(stats) && XplaneOptionsHelper.getOptions().isSmartExclusions()) {
      String exclusions = exclusionsHelper.exportExclusions();
      writer.complete(exclusions);

    } else {
      writer.complete(null);
    }

    if (!StatsHelper.isTileEmpty(stats)) {
      Osm2xpLogger.info(
          "Tile "
              + (int) currentTile.x
              + "/"
              + (int) currentTile.y
              + " stats : "
              + stats.getBuildingsNumber()
              + " buildings, "
              + stats.getForestsNumber()
              + " forests, "
              + stats.getStreetlightsNumber()
              + " street lights, "
              + stats.getObjectsNumber()
              + " objects. (generation took "
              + MiscUtils.getTimeDiff(startTime, new Date())
              + ")");

      // stats
      try {
        if (XplaneOptionsHelper.getOptions().isGenerateXmlStats()
            || XplaneOptionsHelper.getOptions().isGeneratePdfStats()) {
          StatsHelper.getStatsList().add(stats);
        }
        if (XplaneOptionsHelper.getOptions().isGenerateXmlStats()) {
          StatsHelper.saveStats(folderPath, currentTile, stats);
        }
        if (XplaneOptionsHelper.getOptions().isGeneratePdfStats()) {
          StatsHelper.generatePdfReport(folderPath, stats);
        }
      } catch (Osm2xpBusinessException e) {
        Osm2xpLogger.error("Error saving stats file for tile " + currentTile, e);
      }
    } else if (!GuiOptionsHelper.getOptions().isSinglePass()) {
      Osm2xpLogger.info(
          "Tile "
              + (int) currentTile.x
              + "/"
              + (int) currentTile.y
              + " is empty, no dsf generated");
    }
  }
 /**
  * send a streetLight in the dsf file.
  *
  * @param osmPolygon osm polygon
  * @return true if a streetlight has been written in the dsf file.
  */
 private boolean processStreetLights(OsmPolygon osmPolygon) {
   Boolean result = false;
   if (XplaneOptionsHelper.getOptions().isGenerateStreetLights()
       && OsmUtils.isTagInTagsList("highway", "residential", osmPolygon.getTags())) {
     writeStreetLightToDsf(osmPolygon);
     result = true;
   }
   return result;
 }
 @Override
 public void init() {
   // writer initialization
   writer.init(currentTile);
   // exclusionHelper
   if (XplaneOptionsHelper.getOptions().isSmartExclusions()) {
     exclusionsHelper.run();
   }
 }
 /**
  * @param way
  * @param polygon
  * @return
  */
 private boolean processForest(OsmPolygon osmPolygon) {
   Boolean result = false;
   if (XplaneOptionsHelper.getOptions().isGenerateFor()) {
     Integer[] forestIndexAndDensity =
         dsfObjectsProvider.getRandomForestIndexAndDensity(osmPolygon.getTags());
     if (forestIndexAndDensity != null) {
       writeForestToDsf(osmPolygon, forestIndexAndDensity);
       result = true;
     }
   }
   return result;
 }
  private void processLightObject(OsmPolygon poly) {
    if (XplaneOptionsHelper.getOptions().isGenerateLights()) {
      XplaneDsfObject object = dsfObjectsProvider.getRandomDsfLightObject(poly);
      if (object != null) {
        object.setPolygon(poly);
        try {
          writeObjectToDsf(object);

        } catch (Osm2xpBusinessException e) {
        }
      }
    }
  }
  /**
   * Write streetlight objects in dsf file.
   *
   * @param osmPolygon osm road polygon
   */
  public void writeStreetLightToDsf(OsmPolygon osmPolygon) {
    // init d'un entier pour modulo densité street lights
    Integer densityIndex = 0;
    if (XplaneOptionsHelper.getOptions().getLightsDensity() == 0) {
      densityIndex = 10;
    } else {
      if (XplaneOptionsHelper.getOptions().getLightsDensity() == 1) {
        densityIndex = 5;
      } else {
        if (XplaneOptionsHelper.getOptions().getLightsDensity() == 2) densityIndex = 3;
      }
    }
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < osmPolygon.getPolygon().getVertices().size(); i++) {
      if ((i % densityIndex) == 0) {
        Point2D lightLoc = osmPolygon.getPolygon().getVertex(i);
        lightLoc.x = lightLoc.x + 0.0001;
        lightLoc.y = lightLoc.y + 0.0001;
        if (GeomUtils.compareCoordinates(lightLoc, currentTile)) {
          Random randomGenerator = new Random();
          int orientation = randomGenerator.nextInt(360);
          sb.append(
              "OBJECT "
                  + dsfObjectsProvider.getRandomStreetLightObject()
                  + " "
                  + (lightLoc.y)
                  + " "
                  + (lightLoc.x)
                  + " "
                  + orientation);
          sb.append(System.getProperty("line.separator"));
          // stats
          StatsHelper.addStreetLight(stats);
        }
      }
    }

    writer.write(sb.toString());
  }
  /**
   * Construct and write a facade building in the dsf file.
   *
   * @param osmPolygon osm polygon
   * @return true if a building has been gennerated in the dsf file.
   */
  private boolean processBuilding(OsmPolygon osmPolygon) {
    Boolean result = false;
    if (XplaneOptionsHelper.getOptions().isGenerateBuildings()
        && OsmUtils.isBuilding(osmPolygon.getTags())
        && !OsmUtils.isExcluded(osmPolygon.getTags(), osmPolygon.getId())
        && osmPolygon.getPolygon().getVertexNumber() > BUILDING_MIN_VECTORS
        && osmPolygon.getPolygon().getVertexNumber() < BUILDING_MAX_VECTORS) {

      // check that the largest vector of the building
      // and that the area of the osmPolygon.getPolygon() are over the
      // minimum values set by the user
      Double maxVector = osmPolygon.getMaxVectorSize();
      if (maxVector > XplaneOptionsHelper.getOptions().getMinHouseSegment()
          && maxVector < XplaneOptionsHelper.getOptions().getMaxHouseSegment()
          && ((osmPolygon.getPolygon().getArea() * 100000) * 100000)
              > XplaneOptionsHelper.getOptions().getMinHouseArea()) {

        // simplify shape if checked and if necessary
        if (GuiOptionsHelper.getOptions().isSimplifyShapes() && !osmPolygon.isSimplePolygon()) {
          osmPolygon.simplifyPolygon();
        }

        // compute height and facade dsf index
        osmPolygon.setHeight(computeBuildingHeight(osmPolygon));
        Integer facade = computeFacadeIndex(osmPolygon);
        // write building in dsf file
        writeBuildingToDsf(osmPolygon, facade);
        // Smart exclusions
        if (XplaneOptionsHelper.getOptions().isSmartExclusions()) {
          exclusionsHelper.addTodoPolygon(osmPolygon);
          exclusionsHelper.run();
        }
        result = true;
      }
    }
    return result;
  }
  /**
   * Compute the height for this polygon and osm tags.
   *
   * @return Integer the height.
   */
  private Integer computeBuildingHeight(OsmPolygon polygon) {
    Integer result = null;
    Integer osmHeight = polygon.getHeight();
    if (osmHeight != null) {
      result = osmHeight;
    } else {

      if (polygon.getArea() * 10000000 < 0.2) {
        result = XplaneOptionsHelper.getOptions().getResidentialMin();
      } else {
        if (polygon.getArea() * 10000000 > ASSERTION_RESIDENTIAL_MAX_AREA)
          result =
              MiscUtils.getRandomSize(
                  XplaneOptionsHelper.getOptions().getBuildingMin(),
                  XplaneOptionsHelper.getOptions().getBuildingMax());
        else if (polygon.getArea() * 10000000 < ASSERTION_RESIDENTIAL_MAX_AREA)
          result =
              MiscUtils.getRandomSize(
                  XplaneOptionsHelper.getOptions().getResidentialMin(),
                  XplaneOptionsHelper.getOptions().getResidentialMax());
      }
    }
    return result;
  }
  /**
   * choose and write a 3D object in the dsf file.
   *
   * @param polygon osm polygon.
   * @return true if a 3D object has been written in the dsf file.
   */
  private boolean process3dObject(OsmPolygon osmPolygon) {
    Boolean result = false;

    if (XplaneOptionsHelper.getOptions().isGenerateObj()) {
      // simplify shape if checked and if necessary
      if (GuiOptionsHelper.getOptions().isSimplifyShapes() && !osmPolygon.isSimplePolygon()) {
        osmPolygon.simplifyPolygon();
      }
      XplaneDsfObject object = dsfObjectsProvider.getRandomDsfObject(osmPolygon);
      if (object != null) {
        object.setPolygon(osmPolygon);
        try {
          writeObjectToDsf(object);
          result = true;
        } catch (Osm2xpBusinessException e) {
          result = false;
        }
      }
    }
    return result;
  }
  /**
   * compute the dsf index of the residential facade object used for a given polygon
   *
   * @param polygon
   * @param height
   * @return Integer the facade index
   */
  private Integer computeResidentialFacadeIndex(OsmPolygon osmPolygon) {
    Integer result = null;
    // we check if we can use a sloped roof if the user wants them
    if (XplaneOptionsHelper.getOptions().isGenerateSlopedRoofs() && osmPolygon.isSimplePolygon()) {
      result = dsfObjectsProvider.computeFacadeDsfIndex(true, true, true, osmPolygon);
    }
    // no sloped roof, so we'll use a standard house facade
    else {

      // if the polygon is a simple rectangle, we'll use a facade made for
      // simple shaped buildings
      if (osmPolygon.getPolygon().getEdges().size() == 4) {
        result = dsfObjectsProvider.computeFacadeDsfIndex(true, true, false, osmPolygon);
      }
      // the building has a complex footprint, so we'll use a facade made
      // for this case
      else {
        result = dsfObjectsProvider.computeFacadeDsfIndex(false, true, false, osmPolygon);
      }
    }
    return result;
  }
 @Override
 public void processNode(Node node) throws Osm2xpBusinessException {
   // process the node if we're on a single pass mode.
   // if not on single pass, only process if the node is on the current
   // lat/long tile
   if (XplaneOptionsHelper.getOptions().isGenerateObj()) {
     if ((!GuiOptionsHelper.getOptions().isSinglePass()
             && GeomUtils.compareCoordinates(currentTile, node))
         || GuiOptionsHelper.getOptions().isSinglePass()) {
       // write a 3D object in the dsf file if this node is in an
       // object
       // rule
       XplaneDsf3DObject object =
           dsfObjectsProvider.getRandomDsfObjectIndexAndAngle(node.getTag(), node.getId());
       if (object != null) {
         List<Node> nodes = new ArrayList<Node>();
         nodes.add(node);
         object.setPolygon(new OsmPolygon(node.getId(), node.getTag(), nodes));
         writeObjectToDsf(object);
       }
     }
   }
 }