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

org.opentripplanner.ext.traveltime.geometry.SparseMatrixZSampleGrid Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.ext.traveltime.geometry;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import org.locationtech.jts.geom.Coordinate;

/**
 * A generic indexed grid of Z samples.
 *
 * Internally use a SparseMatrix to store samples.
 *
 * @author laurent
 */
public final class SparseMatrixZSampleGrid
  implements ZSampleGrid, DelaunayTriangulation {

  private final class SparseMatrixSamplePoint implements ZSamplePoint, DelaunayPoint {

    private int x;

    private int y;

    private TZ z;

    private SparseMatrixSamplePoint up, down, right, left;

    private GridDelaunayEdge eUp, eUpRight, eRight;

    @Override
    public ZSamplePoint up() {
      return up;
    }

    @Override
    public ZSamplePoint down() {
      return down;
    }

    @Override
    public ZSamplePoint right() {
      return right;
    }

    @Override
    public ZSamplePoint left() {
      return left;
    }

    @Override
    public Coordinate getCoordinates() {
      return SparseMatrixZSampleGrid.this.getCoordinates(this);
    }

    @Override
    public int getX() {
      return this.x;
    }

    @Override
    public int getY() {
      return this.y;
    }

    @Override
    public TZ getZ() {
      return this.z;
    }

    @Override
    public void setZ(TZ z) {
      this.z = z;
    }
  }

  private final class GridDelaunayEdge implements DelaunayEdge {

    private static final int TYPE_VERTICAL = 0;

    private static final int TYPE_HORIZONTAL = 1;

    private static final int TYPE_DIAGONAL = 2;

    private boolean processed;

    private final SparseMatrixSamplePoint A, B;

    private GridDelaunayEdge ccw1, ccw2, cw1, cw2;

    private final int type;

    private GridDelaunayEdge(SparseMatrixSamplePoint A, SparseMatrixSamplePoint B, int type) {
      this.A = A;
      this.B = B;
      switch (type) {
        case TYPE_HORIZONTAL -> A.eRight = this;
        case TYPE_VERTICAL -> A.eUp = this;
        case TYPE_DIAGONAL -> A.eUpRight = this;
      }
      this.type = type;
    }

    @Override
    public DelaunayPoint getA() {
      return A;
    }

    @Override
    public DelaunayPoint getB() {
      return B;
    }

    @Override
    public DelaunayEdge getEdge1(boolean ccw) {
      return ccw ? ccw1 : cw1;
    }

    @Override
    public DelaunayEdge getEdge2(boolean ccw) {
      return ccw ? ccw2 : cw2;
    }

    @Override
    public boolean isProcessed() {
      return processed;
    }

    @Override
    public void setProcessed(boolean processed) {
      this.processed = processed;
    }

    @Override
    public String toString() {
      return "" + B.getCoordinates() + ">";
    }
  }

  private final double dX, dY;

  private final Coordinate center;

  private final SparseMatrix allSamples;

  private List triangulation = null;

  /**
   * @param chunkSize SparseMatrix chunk side (eg 8 or 16). See SparseMatrix.
   * @param totalSize Total estimated size for pre-allocating.
   * @param dX X grid size, same units as center coordinates.
   * @param dY Y grid size, same units as center coordinates.
   * @param center Center position of the grid. Do not need to be precise.
   */
  public SparseMatrixZSampleGrid(
    int chunkSize,
    int totalSize,
    double dX,
    double dY,
    Coordinate center
  ) {
    this.center = center;
    this.dX = dX;
    this.dY = dY;
    allSamples = new SparseMatrix<>(chunkSize, totalSize);
  }

  public ZSamplePoint getOrCreate(int x, int y) {
    SparseMatrixSamplePoint A = allSamples.get(x, y);
    if (A != null) return A;
    A = new SparseMatrixSamplePoint();
    A.x = x;
    A.y = y;
    A.z = null;
    SparseMatrixSamplePoint Aup = allSamples.get(x, y + 1);
    if (Aup != null) {
      Aup.down = A;
      A.up = Aup;
    }
    SparseMatrixSamplePoint Adown = allSamples.get(x, y - 1);
    if (Adown != null) {
      Adown.up = A;
      A.down = Adown;
    }
    SparseMatrixSamplePoint Aright = allSamples.get(x + 1, y);
    if (Aright != null) {
      Aright.left = A;
      A.right = Aright;
    }
    SparseMatrixSamplePoint Aleft = allSamples.get(x - 1, y);
    if (Aleft != null) {
      Aleft.right = A;
      A.left = Aleft;
    }
    allSamples.put(x, y, A);
    return A;
  }

  @Override
  @Nonnull
  public Iterator> iterator() {
    return new Iterator<>() {
      private final Iterator iterator = allSamples.iterator();

      @Override
      public boolean hasNext() {
        return iterator.hasNext();
      }

      @Override
      public ZSamplePoint next() {
        return iterator.next();
      }

      @Override
      public void remove() {
        iterator.remove();
      }
    };
  }

  @Override
  public Coordinate getCoordinates(ZSamplePoint point) {
    // TODO Cache the coordinates in the point?
    return new Coordinate(point.getX() * dX + center.x, point.getY() * dY + center.y);
  }

  @Override
  public int[] getLowerLeftIndex(Coordinate C) {
    return new int[] {
      (int) Math.round((C.x - center.x - dX / 2) / dX),
      (int) Math.round((C.y - center.y - dY / 2) / dY),
    };
  }

  @Override
  public Coordinate getCenter() {
    return center;
  }

  @Override
  public Coordinate getCellSize() {
    return new Coordinate(dX, dY);
  }

  @Override
  public int getXMin() {
    return allSamples.xMin;
  }

  @Override
  public int getXMax() {
    return allSamples.xMax;
  }

  @Override
  public int getYMin() {
    return allSamples.yMin;
  }

  @Override
  public int getYMax() {
    return allSamples.yMax;
  }

  @Override
  public int size() {
    return allSamples.size();
  }

  @Override
  public int edgesCount() {
    if (triangulation == null) {
      delaunify();
    }
    return triangulation.size();
  }

  @Override
  public Iterable> edges() {
    if (triangulation == null) {
      delaunify();
    }
    return triangulation;
  }

  /**
   * The conversion from a grid of points to a Delaunay triangulation is trivial. Each square from
   * the grid is cut through one diagonal in two triangles, the resulting output is a Delaunay
   * triangulation.
   */
  private void delaunify() {
    triangulation = new ArrayList<>(allSamples.size() * 3);
    // 1. Create unlinked edges
    for (SparseMatrixSamplePoint A : allSamples) {
      SparseMatrixSamplePoint B = (SparseMatrixSamplePoint) A.right();
      SparseMatrixSamplePoint D = (SparseMatrixSamplePoint) A.up();
      SparseMatrixSamplePoint C = (SparseMatrixSamplePoint) (
        B != null ? B.up() : D != null ? D.right() : null
      );
      if (B != null) {
        triangulation.add(new GridDelaunayEdge(A, B, GridDelaunayEdge.TYPE_HORIZONTAL));
      }
      if (D != null) {
        triangulation.add(new GridDelaunayEdge(A, D, GridDelaunayEdge.TYPE_VERTICAL));
      }
      if (C != null) {
        triangulation.add(new GridDelaunayEdge(A, C, GridDelaunayEdge.TYPE_DIAGONAL));
      }
    }
    // 2. Link edges
    for (GridDelaunayEdge e : triangulation) {
      switch (e.type) {
        case GridDelaunayEdge.TYPE_HORIZONTAL -> {
          e.ccw1 = e.B.eUp;
          e.ccw2 = e.A.eUpRight;
          e.cw1 = e.A.down == null ? null : e.A.down.eUpRight;
          e.cw2 = e.A.down == null ? null : e.A.down.eUp;
        }
        case GridDelaunayEdge.TYPE_VERTICAL -> {
          e.ccw1 = e.A.left == null ? null : e.A.left.eUpRight;
          e.ccw2 = e.A.left == null ? null : e.A.left.eRight;
          e.cw1 = e.B.eRight;
          e.cw2 = e.A.eUpRight;
        }
        case GridDelaunayEdge.TYPE_DIAGONAL -> {
          e.ccw1 = e.A.up == null ? null : e.A.up.eRight;
          e.ccw2 = e.A.eUp;
          e.cw1 = e.A.right == null ? null : e.A.right.eUp;
          e.cw2 = e.A.eRight;
        }
      }
    }
  }

  @Override
  public DelaunayTriangulation delaunayTriangulate() {
    // We ourselves are a DelaunayTriangulation
    return this;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy