1 package org.simantics.maps.elevation.server;
3 import java.awt.geom.Rectangle2D;
4 import java.io.Closeable;
5 import java.io.IOException;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.util.Collection;
11 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.concurrent.atomic.AtomicInteger;
13 import java.util.stream.Collectors;
14 import java.util.stream.Stream;
16 import org.geotools.geometry.DirectPosition2D;
17 import org.geotools.geometry.Envelope2D;
18 import org.geotools.geometry.jts.ReferencedEnvelope;
19 import org.geotools.referencing.CRS;
20 import org.opengis.geometry.DirectPosition;
21 import org.opengis.referencing.crs.CoordinateReferenceSystem;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
25 import com.github.benmanes.caffeine.cache.Caffeine;
26 import com.github.benmanes.caffeine.cache.LoadingCache;
27 import com.vividsolutions.jts.geom.Coordinate;
28 import com.vividsolutions.jts.geom.Envelope;
29 import com.vividsolutions.jts.index.strtree.STRtree;
31 public class TiffTileInterface implements Closeable {
33 private static final Logger LOGGER = LoggerFactory.getLogger(TiffTileInterface.class);
35 private Path tilesFolder;
36 private Map<Path, Envelope> envelopes = new ConcurrentHashMap<>();
37 private LoadingCache<Path, TiffInterface> interfaceCache;
38 private int openInterfacesSize;
39 private STRtree index;
41 public TiffTileInterface(Path tilesFolder) {
45 public TiffTileInterface(Path tilesFolder, int openInterfacesSize) {
46 if (!Files.isDirectory(tilesFolder)) {
47 throw new IllegalArgumentException("tilesFolder has to be a folder: " + tilesFolder.toAbsolutePath());
49 this.tilesFolder = tilesFolder;
50 this.index = new STRtree();
51 this.openInterfacesSize = openInterfacesSize;
53 this.interfaceCache = Caffeine.newBuilder()
54 .maximumSize(this.openInterfacesSize)
55 .removalListener((key, gdalInterface, cause) -> ((TiffInterface) gdalInterface).close())
56 .build(key -> new TiffInterface(key));
60 } catch (IOException e) {
61 LOGGER.error("Could not initialize index for folder {}", tilesFolder, e);
65 private TiffInterface openTifInterface(Path tifFile) {
66 return interfaceCache.get(tifFile);
69 private Stream<Path> allTiffFiles() throws IOException {
70 return Files.walk(tilesFolder).filter(Files::isRegularFile).filter(tif -> tif.getFileName().toString().endsWith(".tif"));
73 public void initializeIndex() throws IOException {
74 LOGGER.info("Initializing index..");
75 AtomicInteger counter = new AtomicInteger();
76 allTiffFiles().parallel().forEach(tifFile -> {
77 TiffInterface tifInterface = openTifInterface(tifFile);
78 Envelope2D coords = tifInterface.getCornerCoords();
80 ReferencedEnvelope refEnv = new ReferencedEnvelope(coords);
81 ReferencedEnvelope targetEnv = refEnv.transform(c4326, false, 30);
84 index.insert(targetEnv, tifFile);
86 envelopes.put(tifFile, targetEnv);
87 } catch (Exception e) {
88 LOGGER.error("Could not initialize index for file {}", tifFile, e);
91 int current = counter.getAndIncrement();
92 if (current % 100 == 0) {
93 LOGGER.info(" {}", current);
99 public Collection<Rectangle2D> getBoundingBoxes() {
100 Collection<Rectangle2D> rects = envelopes.values().stream().map(env -> {
101 double x = env.getMinX();
102 double y = env.getMinY();
103 return new Rectangle2D.Double(x, y, env.getMaxX() - x, env.getMaxY() - y);
104 }).collect(Collectors.toList());
108 private static CoordinateReferenceSystem c4326;
112 c4326 = CRS.decode("EPSG:4326");
113 } catch (Exception e) {
114 LOGGER.error("Could not initialize epsg:4326", e);
118 public Number lookup(double x, double y) {
119 LOGGER.trace("Looking up x={} y={}", x, y);
120 DirectPosition p = new DirectPosition2D(c4326, x, y);
121 List<Path> tifFiles = (List<Path>) index.query(new Envelope(new Coordinate(x, y)));
122 for (Path tifFile : tifFiles) {
123 TiffInterface tifInterface = openTifInterface(tifFile);
124 if (tifInterface.contains(p)) {
126 return tifInterface.lookup(p);
128 tifInterface.close();
131 System.out.println("not found");
134 return new Double(0); // use 0 by default for now
138 public void close() throws IOException {
139 interfaceCache.invalidateAll();
140 interfaceCache.cleanUp();
145 interfaceCache = null;