Skip to content
Snippets Groups Projects
Commit 1b4fc6fb authored by Luc Maisonobe's avatar Luc Maisonobe
Browse files

Implemented TilesCache.

parent 391ab6ae
No related branches found
No related tags found
No related merge requests found
...@@ -18,12 +18,13 @@ package org.orekit.rugged.core.dem; ...@@ -18,12 +18,13 @@ package org.orekit.rugged.core.dem;
/** Interface representing a factory for raster tile. /** Interface representing a factory for raster tile.
* @param <T> Type of tiles.
* @author Luc Maisonobe * @author Luc Maisonobe
*/ */
public interface TileFactory { public interface TileFactory<T extends Tile> {
/** Create an empty tile. /** Create an empty tile.
*/ */
Tile createTile(); T createTile();
} }
/* Copyright 2013-2014 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You 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 org.orekit.rugged.core.dem;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.orekit.rugged.api.TileUpdater;
/** Cache for Digital Elevation Model {@link Tile tiles}.
* <p>
* Beware, this cache is <em>not</em> thread-safe!
* </p>
* @param <T> Type of tiles.
* @author Luc Maisonobe
*/
public class TilesCache<T extends Tile> {
/** Factory for empty tiles. */
private final TileFactory<T> factory;
/** Updater for retrieving tiles data. */
private final TileUpdater updater;
/** Search optimized cache. */
private List<TilesStrip> searchCache;
/** Eviction optimized cache. */
private T[] evictionCache;
/** Index for next tile. */
private int next;
/** Simple constructor.
* @param factory factory for creating empty tiles
* @param updater updater for retrieving tiles data
* @param maxTiles maximum number of tiles stored simultaneously in the cache
*/
public TilesCache(final TileFactory<T> factory, final TileUpdater updater, final int maxTiles) {
this.factory = factory;
this.updater = updater;
this.searchCache = new ArrayList<TilesStrip>();
@SuppressWarnings("unchecked")
final T[] array = (T[]) Array.newInstance(Tile.class, maxTiles);
this.evictionCache = array;
this.next = 0;
}
/** Get the tile covering a ground point.
* @param latitude ground point latitude
* @param longitude ground point longitude
* @return tile covering the ground point
*/
public T getTile(final double latitude, final double longitude) {
return getStrip(latitude, longitude).getTile(latitude, longitude);
}
/** Create a tile covering a ground point.
* @param latitude latitude of the point
* @param longitude longitude of the point
* @return new tile covering the point
*/
private T createTile(final double latitude, final double longitude) {
if (evictionCache[next] != null) {
// the tile we are creating will evict this older tile
// we need to remove it from the search cache too
for (final Iterator<TilesStrip> iterator = searchCache.iterator(); iterator.hasNext();) {
if (iterator.next().removeTile(evictionCache[next])) {
break;
}
}
}
// create the tile and retrieve its data
final T tile = factory.createTile();
updater.updateTile(latitude, longitude, tile);
// store the tile in the eviction cache
evictionCache[next] = tile;
next = (next + 1) % evictionCache.length;
return tile;
}
/** Get a strip covering a ground point.
* @param latitude ground point latitude
* @param longitude ground point longitude
* @return strip covering the ground point
*/
private TilesStrip getStrip(final double latitude, final double longitude) {
// look for a strip at the specified latitude
final int index = Collections.binarySearch(searchCache, new BasicLatitudeProvider(latitude),
new LatitudeComparator());
if (index >= 0) {
// rare case, the latitude is an exact maximum latitude for a strip
return searchCache.get(index);
} else {
final int insertionPoint = -(index + 1);
if (insertionPoint < searchCache.size()) {
final TilesStrip strip = searchCache.get(insertionPoint);
if (strip.covers(latitude)) {
// we have found an existing strip
return strip;
}
}
// no existing strip covers the specified latitude, we need to create a new one
final TilesStrip strip = new TilesStrip(createTile(latitude, longitude));
searchCache.add(insertionPoint, strip);
return strip;
}
}
/** Interface for retrieving latitude. */
private interface LatitudeProvider {
/** Get latitude.
* @return latitude
*/
double getLatitude();
}
/** Basic implementation of {@link LatitudeProvider}. */
private static class BasicLatitudeProvider implements LatitudeProvider {
/** Latitude. */
private final double latitude;
/** Simple constructor.
* @param latitude latitude
*/
public BasicLatitudeProvider(final double latitude) {
this.latitude = latitude;
}
/** {@inheritDoc} */
@Override
public double getLatitude() {
return latitude;
}
}
/** Interface for retrieving longitude. */
private interface LongitudeProvider {
/** Get longitude.
* @return longitude
*/
double getLongitude();
}
/** Basic implementation of {@link LongitudeProvider}. */
private static class BasicLongitudeProvider implements LongitudeProvider {
/** Longitude. */
private final double longitude;
/** Simple constructor.
* @param longitude longitude
*/
public BasicLongitudeProvider(final double longitude) {
this.longitude = longitude;
}
/** {@inheritDoc} */
@Override
public double getLongitude() {
return longitude;
}
}
/** Strip of tiles for a given latitude. */
private class TilesStrip implements LatitudeProvider {
/** Minimum latitude. */
private final double minLatitude;
/** Maximum latitude. */
private final double maxLatitude;
/** Tiles list. */
private final List<TileDecorator> tiles;
/** Simple constructor.
* @param tile first tile to insert in the strip
*/
public TilesStrip(final T tile) {
minLatitude = tile.getMinimumLatitude();
maxLatitude = minLatitude + tile.getLatitudeRows() * tile.getLatitudeStep();
tiles = new ArrayList<TileDecorator>();
tiles.add(new TileDecorator(tile));
}
/** Check if the strip covers a specified latitude.
* @param latitude latitude to check
* @return true if the strip covers the latitude
*/
public boolean covers(final double latitude) {
return (minLatitude <= latitude) && (maxLatitude >= latitude);
}
/** {@inheritDoc} */
@Override
public double getLatitude() {
return maxLatitude;
}
/** Get a tile covering a ground point.
* @param latitude ground point latitude
* @param longitude ground point longitude
* @return strip covering the ground point
*/
public T getTile(final double latitude, final double longitude) {
// look for a tile at the specified longitude
final int index = Collections.binarySearch(tiles, new BasicLongitudeProvider(longitude),
new LongitudeComparator());
if (index >= 0) {
// rare case, the longitude is an exact maximum longitude for a tile
return tiles.get(index).getTile();
} else {
final int insertionPoint = -(index + 1);
if (insertionPoint < tiles.size()) {
final T tile = tiles.get(insertionPoint).getTile();
if (tile.covers(latitude, longitude)) {
// we have found an existing tile
return tile;
}
}
// no existing tile covers the specified ground point, we need to create a new one
final T tile = createTile(latitude, longitude);
tiles.add(insertionPoint, new TileDecorator(tile));
return tile;
}
}
/** Remove a tile from the strip.
* @param tile tile to remove
* @return true if the tile has been removed
*/
public boolean removeTile(final Tile tile) {
for (final Iterator<TileDecorator> iterator = tiles.iterator(); iterator.hasNext();) {
if (iterator.next().getTile() == tile) {
iterator.remove();
return true;
}
}
return false;
}
}
/** Decorator for tiles, implementing {@link LongitudeProvider}. */
private class TileDecorator implements LongitudeProvider {
/** Underlying tile. */
private final T tile;
/** Simple constructor.
* @param tile tile to decorate
*/
public TileDecorator(final T tile) {
this.tile = tile;
}
/** Get the underlying tile.
* @return underlying tile
*/
public T getTile() {
return tile;
}
/** {@inheritDoc} */
@Override
public double getLongitude() {
return tile.getMinimumLongitude() + tile.getLongitudeColumns() * tile.getLongitudeStep();
}
}
/** Comparator for sorting with respect to latitude. */
private static class LatitudeComparator implements Comparator<LatitudeProvider> {
/** {@inheritDoc} */
@Override
public int compare(final LatitudeProvider o1, final LatitudeProvider o2) {
return Double.compare(o1.getLatitude(), o2.getLatitude());
}
}
/** Comparator for sorting with respect to longitude. */
private static class LongitudeComparator implements Comparator<LongitudeProvider> {
/** {@inheritDoc} */
@Override
public int compare(final LongitudeProvider o1, final LongitudeProvider o2) {
return Double.compare(o1.getLongitude(), o2.getLongitude());
}
}
}
/* Copyright 2013-2014 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You 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 orekit.rugged.core.dem;
import org.apache.commons.math3.util.FastMath;
import org.orekit.rugged.api.TileUpdater;
import org.orekit.rugged.api.UpdatableTile;
public class ConstantElevationUpdater implements TileUpdater {
private double size;
private int n;
private double elevation;
public ConstantElevationUpdater(double size, int n, double elevation) {
this.size = size;
this.n = n;
this.elevation = elevation;
}
public void updateTile(double latitude, double longitude, UpdatableTile tile) {
tile.setGeometry(size * FastMath.floor(latitude / size),
size * FastMath.floor(longitude / size),
size / n, size / n, n, n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
tile.setElevation(i, j, elevation);
}
}
}
}
/* Copyright 2013-2014 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You 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 orekit.rugged.core.dem;
import org.orekit.rugged.core.dem.SimpleTile;
import org.orekit.rugged.core.dem.TileFactory;
public class CountingFactory implements TileFactory<SimpleTile> {
private int count;
public CountingFactory() {
count = 0;
}
public SimpleTile createTile() {
++count;
return new SimpleTile();
}
public int getCount() {
return count;
}
}
/* Copyright 2013-2014 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You 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 orekit.rugged.core.dem;
import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.random.Well19937a;
import org.apache.commons.math3.util.FastMath;
import org.junit.Assert;
import org.junit.Test;
import org.orekit.rugged.core.dem.SimpleTile;
import org.orekit.rugged.core.dem.TilesCache;
public class TilesCacheTest {
@Test
public void testSingleTile() {
CountingFactory factory = new CountingFactory();
TilesCache<SimpleTile> cache = new TilesCache<SimpleTile>(factory,
new ConstantElevationUpdater(FastMath.toRadians(3.0), 10, 10.0), 1000);
SimpleTile tile = cache.getTile(FastMath.toRadians(-23.2), FastMath.toRadians(137.5));
Assert.assertEquals(1, factory.getCount());
Assert.assertEquals(-24.0, FastMath.toDegrees(tile.getMinimumLatitude()), 1.0e-10);
Assert.assertEquals(135.0, FastMath.toDegrees(tile.getMinimumLongitude()), 1.0e-10);
Assert.assertEquals( 0.3, FastMath.toDegrees(tile.getLatitudeStep()), 1.0e-10);
Assert.assertEquals( 0.3, FastMath.toDegrees(tile.getLongitudeStep()), 1.0e-10);
}
@Test
public void testEviction() {
CountingFactory factory = new CountingFactory();
TilesCache<SimpleTile> cache = new TilesCache<SimpleTile>(factory,
new ConstantElevationUpdater(FastMath.toRadians(1.0), 10, 10.0), 12);
// fill up the 12 tiles we can keep in cache
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 3; ++j) {
cache.getTile(FastMath.toRadians(0.5 + j), FastMath.toRadians(0.5 + i));
}
}
Assert.assertEquals(12, factory.getCount());
// keep using the same tiles for a while
RandomGenerator generator = new Well19937a(0xf556baa5977435c5l);
for (int i = 0; i < 10000; ++i) {
double lat = 3.0 * generator.nextDouble();
double lon = 4.0 * generator.nextDouble();
cache.getTile(FastMath.toRadians(lat), FastMath.toRadians(lon));
}
Assert.assertEquals(12, factory.getCount());
// ask for one point outside of the covered area, to evict the (0.0, 0.0) tile
cache.getTile(FastMath.toRadians(20.5), FastMath.toRadians(30.5));
Assert.assertEquals(13, factory.getCount());
// ask again for one point in the evicted tile which must be reallocated
cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(0.5));
Assert.assertEquals(14, factory.getCount());
// the 13th allocated tile should still be there
cache.getTile(FastMath.toRadians(20.5), FastMath.toRadians(30.5));
Assert.assertEquals(14, factory.getCount());
// evict all the tiles, goind to a completely different zone
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 3; ++j) {
cache.getTile(FastMath.toRadians(40.5 + i), FastMath.toRadians(90.5 + j));
}
}
Assert.assertEquals(26, factory.getCount());
}
@Test
public void testExactEnd() {
CountingFactory factory = new CountingFactory();
TilesCache<SimpleTile> cache =
new TilesCache<SimpleTile>(factory,
new ConstantElevationUpdater(0.125, 8, 10.0),
12);
SimpleTile regularTile = cache.getTile(0.2, 0.6);
Assert.assertEquals(1, factory.getCount());
Assert.assertEquals(0.125, regularTile.getMinimumLatitude(), 1.0e-10);
Assert.assertEquals(0.5, regularTile.getMinimumLongitude(), 1.0e-10);
Assert.assertEquals(0.015625, regularTile.getLatitudeStep(), 1.0e-10);
Assert.assertEquals(0.015625, regularTile.getLongitudeStep(), 1.0e-10);
SimpleTile tileAtEnd = cache.getTile(0.250, 0.625);
Assert.assertEquals(1, factory.getCount());
Assert.assertEquals(0.125, tileAtEnd.getMinimumLatitude(), 1.0e-10);
Assert.assertEquals(0.5, tileAtEnd.getMinimumLongitude(), 1.0e-10);
Assert.assertEquals(0.015625, tileAtEnd.getLatitudeStep(), 1.0e-10);
Assert.assertEquals(0.015625, tileAtEnd.getLongitudeStep(), 1.0e-10);
}
@Test
public void testNonContiguousFill() {
CountingFactory factory = new CountingFactory();
TilesCache<SimpleTile> cache =
new TilesCache<SimpleTile>(factory,
new ConstantElevationUpdater(FastMath.toRadians(1.0), 10, 10.0),
16);
cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(0.5));
cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(2.5));
cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(3.5));
cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(3.5));
cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(3.5));
cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(1.5));
cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(1.5));
cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(1.5));
cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(3.5));
cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(2.5));
cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(2.5));
cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(2.5));
cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(0.5));
cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(1.5));
cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(0.5));
cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(0.5));
Assert.assertEquals(16, factory.getCount());
// keep using the same tiles for a while
RandomGenerator generator = new Well19937a(0x1c951160de55c9d5l);
for (int i = 0; i < 10000; ++i) {
double lat = 3.0 * generator.nextDouble();
double lon = 4.0 * generator.nextDouble();
cache.getTile(FastMath.toRadians(lat), FastMath.toRadians(lon));
}
Assert.assertEquals(16, factory.getCount());
cache.getTile(FastMath.toRadians(-30.5), FastMath.toRadians(2.5));
Assert.assertEquals(17, factory.getCount());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment