All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.ucr.cs.bdlab.beast.generator.ParcelGenerator.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 University of California, Riverside
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.ucr.cs.bdlab.beast.generator

import edu.ucr.cs.bdlab.beast.geolite.{EnvelopeND, EnvelopeNDLite}
import edu.ucr.cs.bdlab.beast.util.MathUtil
import org.locationtech.jts.geom.Geometry

import scala.collection.mutable

/**
 * Generates boxes according to the parcel generator
 * @param partition
 */
class ParcelGenerator(partition: RandomSpatialPartition)
  extends SpatialGenerator(partition) {

  /**The index of the record that will be returned when next is called*/
  var iRecord: Long = 0

  val splitRange: Double = partition.opts.getDouble(ParcelDistribution.SplitRange, 0.2)

  val dither: Double = partition.opts.getDouble(ParcelDistribution.Dither, 0.2)

  /**Unit square is the input domain*/
  val UnitSquare: EnvelopeNDLite = new EnvelopeNDLite(2, 0, 0, 1, 1)

  /**A stack of boxes to split. Each pair represents the level and the box*/
  var boxesToSplit: mutable.ArrayBuffer[(Int, EnvelopeNDLite)] = mutable.ArrayBuffer((0, UnitSquare))

  /**
   * The level of the deepest box to generate is ⌈ log2(n)⌉ =
   * ⌊log2(n-1)⌋ + 1
   */
  val maxDepth: Int = MathUtil.log2(partition.cardinality - 1) + 1

  /**
   * Number of boxes that will be generated at the deepest level (maxDepth).
   * The remaining records will be generated at level maxDepth - 1
   */
  val numBoxesMaxDepth: Long = 2 * partition.cardinality - (1 << maxDepth)

  /**
   * Generates a box by first generating a point and building a box around it
   * @return
   */
  def generateBox: EnvelopeND = {
    assert(!boxesToSplit.isEmpty)
    assert(iRecord <= partition.cardinality)
    var (level, box) = boxesToSplit.remove(boxesToSplit.length - 1)

    while (iRecord <= partition.cardinality) {
      if (level == maxDepth || level == maxDepth - 1 && iRecord > numBoxesMaxDepth) {
        // Box is final. Return it
        ditherBox(box)
        return new EnvelopeND(geometryFactory, box)
      } else {
        // Split the box into two
        val (box1: EnvelopeNDLite, box2: EnvelopeNDLite) = splitBox(box)
        boxesToSplit.append((level + 1, box2))
        // Update the level and box for the next iteration
        level = level + 1
        box = box1
      }
    }
    null
  }

  /**
   * Split the given box into two according to the splitRange value. The given box is reused and returned as the
   * first box while the second box is created in the function.
   * This function always splits the box along the longest side. Let's assume the longest side has a length l,
   * the split will happen at l * uniform(splitRange, 1-splitRange).
   * @param box the box to split. Also the first box to be returned
   * @return the two boxes that result of the split
   */
  private def splitBox(box: EnvelopeNDLite): (EnvelopeNDLite, EnvelopeNDLite) = {
    val secondBox = new EnvelopeNDLite(box)
    var longestDimension: Int = 0
    for (d <- 1 until box.getCoordinateDimension) {
      if (box.getSideLength(d) > box.getSideLength(longestDimension))
        longestDimension = d
    }
    val splitPoint: Double = box.getMinCoord(longestDimension) +
      box.getSideLength(longestDimension) * uniform(splitRange, 1 - splitRange)
    box.setMaxCoord(longestDimension, splitPoint)
    secondBox.setMinCoord(longestDimension, splitPoint)
    (box, secondBox)
  }

  /**
   * Change the size of the given box along all dimensions according to the dither parameter.
   * The amount of change on the side length is a uniformly random variable between [0, dither).
   * This means that if the dither parameter is zero, the box will not be changed.
   * The center of the box remains fixed while dither
   * @param box the box to be dithered, changed in place.
   */
  private def ditherBox(box: EnvelopeNDLite): Unit = {
    for (d <- 0 until box.getCoordinateDimension) {
      val changeAmount: Double = uniform(0, dither) * box.getSideLength(d)
      box.setMinCoord(d, box.getMinCoord(d) + changeAmount / 2)
      box.setMaxCoord(d, box.getMaxCoord(d) - changeAmount / 2)
    }
  }

  /**
   * Generates and returns the next geometry
   *
   * @return the generated geometry
   */
  override def nextGeometry: Geometry = {
    iRecord += 1
    assert(iRecord <= partition.cardinality || boxesToSplit.isEmpty)
    generateBox
  }

  override def hasNext: Boolean = iRecord < partition.cardinality
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy