/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.common.geometry; import java.util.ArrayList; import java.util.List; import org.opentripplanner.common.geometry.ZSampleGrid.ZSamplePoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; /** * Helper class to fill-in a ZSampleGrid from a given loosely-defined set of sampling points. * * The process is customized by an "accumulative" metric which gives the behavior of cumulating * several values onto one sampling point. * * To use this class, create an instance giving an AccumulativeMetric implementation as parameter. * Then for each source sample, call "addSample" with the its TZ value. At the end, call close() to * close the sample grid (ie add grid node at the edge to make sure borders are correctly defined, * the definition of correct is left to the client). * * @author laurent */ public class AccumulativeGridSampler<TZ> { /** * An accumulative metric give the behavior of combining several samples to a regular sample * grid, ie how we should weight and add several TZ values from inside a cell to compute the * cell corner TZ values. * * @author laurent * @param <TZ> */ public interface AccumulativeMetric<TZ> { /** * Callback function to handle a new added sample. * * @param C0 The initial position of the sample, as given in the addSample() call. * @param Cs The position of the sample on the grid, never farther away than (dX,dY) * @param z The z value of the initial sample, as given in the addSample() call. * @param zS The previous z value of the sample. Can be null if this is the first time, it's * up to the caller to initialize the z value. * @param offRoadSpeed The offroad speed to assume. * @return The modified z value for the sample. */ public TZ cumulateSample(Coordinate C0, Coordinate Cs, TZ z, TZ zS, double offRoadSpeed); /** * Callback function to handle a "closing" sample (that is a sample post-created to surround * existing samples and provide nice and smooth edges for the algorithm). * * @param point The point to set Z. * @return True if the point "close" the set. */ public boolean closeSample(ZSamplePoint<TZ> point); } private static final Logger LOG = LoggerFactory.getLogger(AccumulativeGridSampler.class); private AccumulativeMetric<TZ> metric; private ZSampleGrid<TZ> sampleGrid; private boolean closed = false; /** * @param metric TZ data "behavior" and "metric". */ public AccumulativeGridSampler(ZSampleGrid<TZ> sampleGrid, AccumulativeMetric<TZ> metric) { this.metric = metric; this.sampleGrid = sampleGrid; } public final void addSamplingPoint(Coordinate C0, TZ z, double offRoadSpeed) { if (closed) throw new IllegalStateException("Can't add a sample after closing."); int[] xy = sampleGrid.getLowerLeftIndex(C0); int x = xy[0]; int y = xy[1]; @SuppressWarnings("unchecked") ZSamplePoint<TZ>[] ABCD = new ZSamplePoint[4]; ABCD[0] = sampleGrid.getOrCreate(x, y); ABCD[1] = sampleGrid.getOrCreate(x + 1, y); ABCD[2] = sampleGrid.getOrCreate(x, y + 1); ABCD[3] = sampleGrid.getOrCreate(x + 1, y + 1); for (ZSamplePoint<TZ> P : ABCD) { Coordinate C = sampleGrid.getCoordinates(P); P.setZ(metric.cumulateSample(C0, C, z, P.getZ(), offRoadSpeed)); } } /** * Surround all existing samples on the edge by a layer of closing samples. */ public final void close() { if (closed) return; closed = true; List<ZSamplePoint<TZ>> processList = new ArrayList<ZSamplePoint<TZ>>(sampleGrid.size()); for (ZSamplePoint<TZ> A : sampleGrid) { processList.add(A); } int round = 0; int n = 0; while (!processList.isEmpty()) { List<ZSamplePoint<TZ>> newProcessList = new ArrayList<ZSamplePoint<TZ>>( processList.size()); for (ZSamplePoint<TZ> A : processList) { if (A.right() == null) { ZSamplePoint<TZ> B = closeSample(A.getX() + 1, A.getY()); if (B != null) newProcessList.add(B); n++; } if (A.left() == null) { ZSamplePoint<TZ> B = closeSample(A.getX() - 1, A.getY()); if (B != null) newProcessList.add(B); n++; } if (A.up() == null) { ZSamplePoint<TZ> B = closeSample(A.getX(), A.getY() + 1); if (B != null) newProcessList.add(B); n++; } if (A.down() == null) { ZSamplePoint<TZ> B = closeSample(A.getX(), A.getY() - 1); if (B != null) newProcessList.add(B); n++; } } processList = newProcessList; LOG.debug("Round {} : next process list {}", round, processList.size()); round++; } LOG.info("Added {} closing samples to get a total of {}.", n, sampleGrid.size()); } private final ZSamplePoint<TZ> closeSample(int x, int y) { ZSamplePoint<TZ> A = sampleGrid.getOrCreate(x, y); boolean ok = metric.closeSample(A); if (ok) { return null; } else { return A; } } }