Package org.osm2world.core.map_elevation.creation

Source Code of org.osm2world.core.map_elevation.creation.LeastSquaresInterpolator$SiteWithPolynomial

package org.osm2world.core.map_elevation.creation;

import static java.lang.Math.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.PriorityQueue;

import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.QRDecomposition;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
import org.osm2world.core.math.AxisAlignedBoundingBoxXZ;
import org.osm2world.core.math.VectorXYZ;
import org.osm2world.core.math.VectorXZ;
import org.osm2world.core.math.datastructures.IntersectionGrid;
import org.osm2world.core.math.datastructures.IntersectionTestObject;

/**
* uses least squares method to approximate a polynomial at each site,
* and calculates elevations based on the polynomials at the nearest sites.
*/
public class LeastSquaresInterpolator implements TerrainInterpolator {
 
  private static final double CELL_SIZE = 50; //should only affect performance
  private static final int SITES_FOR_APPROX = 9;
  private static final int SITES_FOR_INTERPOL = 29;
 
  private Collection<SiteWithPolynomial> sites;
  private IntersectionGrid<SiteWithPolynomial> siteGrid; //TODO: rename IntersectionGrid to something more generic
 
  @Override
  public void setKnownSites(Collection<VectorXYZ> siteVectors) {
   
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
   
    sites = new ArrayList<SiteWithPolynomial>(siteVectors.size());
   
    siteGrid = new IntersectionGrid<SiteWithPolynomial>(
        new AxisAlignedBoundingBoxXZ(siteVectors).pad(CELL_SIZE/2),
        CELL_SIZE, CELL_SIZE);
   
    for (VectorXYZ siteVector : siteVectors) {
      SiteWithPolynomial s = new SiteWithPolynomial(siteVector);
      sites.add(s);
      siteGrid.insert(s);
    }
   
    System.out.println("  time grid: " + stopWatch);
    stopWatch.reset();
    stopWatch.start();
   
    /* approximate a polynomial at each site */

    Map<SiteWithPolynomial, List<SiteWithPolynomial>> nearestSiteMap
      = new HashMap<SiteWithPolynomial, List<SiteWithPolynomial>>();

    for (SiteWithPolynomial site : sites) {
     
      List<SiteWithPolynomial> nearestSites =
          findNearestSites(site.pos.xz(), SITES_FOR_APPROX, false);

      nearestSiteMap.put(site, nearestSites);
     
    }
   
    System.out.println("  time nearest sites: " + stopWatch);
    stopWatch.reset();
    stopWatch.start();

    calculatePolynomials:
    for (SiteWithPolynomial site : sites) {
     
      List<SiteWithPolynomial> nearestSites =
          nearestSiteMap.get(site);
     
      RealVector vector = new ArrayRealVector(SITES_FOR_APPROX);
      RealMatrix matrix = new Array2DRowRealMatrix(
          SITES_FOR_APPROX, DefaultPolynomial.NUM_COEFFS);
     
      for (int row = 0; row < SITES_FOR_APPROX; row++) {
        SiteWithPolynomial nearSite = nearestSites.get(row);
        DefaultPolynomial.populateMatrix(matrix, row, nearSite.pos.x, nearSite.pos.z);
        vector.setEntry(row, nearSite.pos.y);
      }
     
      QRDecomposition qr = new QRDecomposition(matrix);
      RealVector solution = qr.getSolver().solve(vector);
       
      double[] coeffs = solution.toArray();
     
      for (double coeff : coeffs) {
        if (coeff > 10e3) {
          continue calculatePolynomials;
        }
      }
     
      site.setPolynomial(new DefaultPolynomial(coeffs));
     
    }

    System.out.println("  time polyonmials: " + stopWatch);
    stopWatch.reset();
    stopWatch.start();
   
  }
 
  @Override
  public VectorXYZ interpolateEle(VectorXZ pos) {
   
    List<SiteWithPolynomial> nearestSites =
        findNearestSites(pos, SITES_FOR_INTERPOL, true);
   
    double eleSum = 0;
    double weightSum = 0;
   
    for (SiteWithPolynomial site : nearestSites) {
     
      double distance = site.pos.distanceToXZ(pos);
     
      double weight = max(1 - distance / 120, 0);
     
      weightSum += weight;
           
      eleSum += weight * site.getPolynomial().evaluateAt(pos.x, pos.z);
     
    }
   
    double ele = eleSum / weightSum;
   
    return pos.xyz(ele);
   
  }
 
  /**
   * provides access to the polynomials approximated internally.
   * This is usually only interesting for debugging or similar tasks.
   */
  public Collection<SiteWithPolynomial> getSitesWithPolynomials() {
    return sites;
  }
 
  private List<SiteWithPolynomial> findNearestSites(
      final VectorXZ pos, int numberSites, boolean requirePolynomial) {

    PriorityQueue<SiteWithPolynomial> result =
        new PriorityQueue<SiteWithPolynomial>(numberSites,
            new Comparator<SiteWithPolynomial>() {
          @Override
          public int compare(SiteWithPolynomial s1, SiteWithPolynomial s2) {
            return - Double.compare(s1.pos.distanceToXZ(pos),
                s2.pos.distanceToXZ(pos));
          }
        });
   
    Collection<SiteWithPolynomial>[][] cellArray = siteGrid.getCellArray();
    int cellX = siteGrid.cellXForCoord(pos.x, pos.z);
    int cellZ = siteGrid.cellZForCoord(pos.x, pos.z);
     
    int cellRange = 0;
       
    do {
     
      for (int i = max(cellX-cellRange, 0); i < min(cellX+cellRange+1, cellArray.length); i++) {
        for (int j = max(cellZ-cellRange, 0); j < min(cellZ+cellRange+1, cellArray[i].length); j++) {
         
          //needs to be on the outer ring of cells (others have been checked before)
          if (i == cellX-cellRange || i == cellX+cellRange
              || j == cellZ-cellRange || j == cellZ+cellRange) {
         
            Collection<SiteWithPolynomial> sitesInCell = cellArray[i][j];

            if (sitesInCell != null) {
              for (SiteWithPolynomial site : sitesInCell) {
               
                if (requirePolynomial && site.polynomial == null) continue;
               
                if (result.size() < numberSites) {
                 
                  result.add(site);
                 
                } else if (site.pos.distanceToXZ(pos) <
                    result.peek().pos.distanceToXZ(pos)) {
                 
                  result.remove();
                  result.add(site);
                 
                }
              }
            }
           
          }
         
        }
      }
     
      cellRange ++;
     
    } while (result.size() < numberSites
        || cellRange * CELL_SIZE < result.peek().pos.distanceToXZ(pos));

    //TODO error handling (not enough sites)
   
    /* get the result as a list with ascending distance */
   
    List<SiteWithPolynomial> resultList =
        new ArrayList<SiteWithPolynomial>(result);
   
    Collections.reverse(resultList);
   
    return resultList;
   
  }
 
  public static interface Polynomial {
   
    public double evaluateAt(double x, double z);
   
  }
   
  public static final class DefaultPolynomial implements Polynomial {
   
    private static final int NUM_COEFFS = 6;
   
    private final double[] coeffs;
   
    private DefaultPolynomial(double[] coeffs) {
      assert coeffs.length == NUM_COEFFS;
      this.coeffs = coeffs;
    }
   
    @Override
    public double evaluateAt(double x, double z) {
      return coeffs[0]
          + coeffs[1] * x
          + coeffs[2] * z
          + coeffs[3] * x*x
          + coeffs[4] * x*z
          + coeffs[5] * z*z;
    }
   
    public static void populateMatrix(RealMatrix matrix, int row,
        double x, double z) {
     
      matrix.setEntry(row, 0, 1);
      matrix.setEntry(row, 1, x);
      matrix.setEntry(row, 2, z);
      matrix.setEntry(row, 3, x*x);
      matrix.setEntry(row, 4, x*z);
      matrix.setEntry(row, 5, z*z);
     
    }
   
    @Override
    public String toString() {
      return String.format(Locale.US,
          "%.3f + %.3fx + %.3fz + %.3fx^2 + %.3fxz + %.3fz^2",
          coeffs[0], coeffs[1], coeffs[2],
          coeffs[3], coeffs[4], coeffs[5]);
    }
   
  }
 
  public static final class SiteWithPolynomial implements IntersectionTestObject {
   
    public final VectorXYZ pos;
    private Polynomial polynomial;
       
    public SiteWithPolynomial(VectorXYZ site) {
      this.pos = site;
    }
       
    public Polynomial getPolynomial() {
      return polynomial;
    }
   
    public void setPolynomial(Polynomial polynomial) {
      this.polynomial = polynomial;
    }
   
    @Override
    public AxisAlignedBoundingBoxXZ getAxisAlignedBoundingBoxXZ() {
      return pos.getAxisAlignedBoundingBoxXZ();
    }
   
    @Override
    public String toString() {
      return String.format("{%s, %s}", pos.toString(), polynomial);
    }
   
  }
 
}
TOP

Related Classes of org.osm2world.core.map_elevation.creation.LeastSquaresInterpolator$SiteWithPolynomial

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.