package com.revolsys.elevation.gridded.compactbinary;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.util.Set;
import com.google.common.collect.Sets;
import com.revolsys.elevation.gridded.DirectFileElevationModel;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.gis.grid.RectangularMapGrid;
import com.revolsys.io.Buffers;
import com.revolsys.io.channels.ChannelReader;
import com.revolsys.io.channels.ChannelWriter;
import com.revolsys.io.file.Paths;
import com.revolsys.util.Exceptions;
public class CompactBinaryGriddedElevationModelFile extends DirectFileElevationModel {
private static final int ELEVATION_BYTE_COUNT = 4;
public static CompactBinaryGriddedElevationModelFile newModel(final Path basePath,
final GeometryFactory geometryFactory, final int minX, final int minY, final int gridWidth,
final int gridHeight, final int gridCellSize) {
final Path rowDirectory = basePath.resolve(Integer.toString(minX));
final int coordinateSystemId = geometryFactory.getCoordinateSystemId();
final String fileName = RectangularMapGrid.getTileFileName("dem", coordinateSystemId,
Integer.toString(gridCellSize * gridWidth), minX, minY, "demcs");
final Path path = rowDirectory.resolve(fileName);
return new CompactBinaryGriddedElevationModelFile(path, geometryFactory, minX, minY, gridWidth,
gridHeight, gridCellSize);
}
private final Path path;
private FileChannel channel;
private ChannelReader reader;
private final Set<OpenOption> openOptions;
private final FileAttribute<?>[] fileAttributes = Paths.FILE_ATTRIBUTES_NONE;
private final ByteBuffer buffer = ByteBuffer.allocateDirect(ELEVATION_BYTE_COUNT);
private ByteBuffer rowBuffer;
private double scaleZ;
private boolean createMissing = false;
private boolean useLocks = false;
public CompactBinaryGriddedElevationModelFile(final Path path) {
super(CompactBinaryGriddedElevation.HEADER_SIZE, CompactBinaryGriddedElevation.RECORD_SIZE);
this.path = path;
this.openOptions = Paths.OPEN_OPTIONS_READ_SET;
readHeader();
}
public CompactBinaryGriddedElevationModelFile(final Path path,
final GeometryFactory geometryFactory, final int minX, final int minY, final int gridWidth,
final int gridHeight, final int gridCellSize) {
super(geometryFactory, minX, minY, gridWidth, gridHeight, gridCellSize,
CompactBinaryGriddedElevation.HEADER_SIZE, 4);
setZBoundsUpdateRequired(false);
this.openOptions = Sets.newHashSet(StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.SYNC);
this.createMissing = true;
this.path = path;
this.scaleZ = geometryFactory.getScaleZ();
if (this.scaleZ <= 0) {
this.scaleZ = 1000;
}
}
@Override
public void close() {
super.close();
final FileChannel fileChannel = this.channel;
this.channel = null;
if (fileChannel != null) {
try {
fileChannel.close();
} catch (final IOException e) {
}
}
}
protected void createNewFile() throws IOException {
Paths.createParentDirectories(this.path);
this.channel = FileChannel.open(this.path, Paths.OPEN_OPTIONS_READ_WRITE_SET,
this.fileAttributes);
final ChannelWriter writer = new ChannelWriter(this.channel);
final int gridWidth = getGridWidth();
final int gridHeight = getGridHeight();
final int gridCellSize = getGridCellSize();
final BoundingBox boundingBox = getBoundingBox();
final GeometryFactory geometryFactory = getGeometryFactory();
CompactBinaryGriddedElevationWriter.writeHeader(writer, boundingBox, geometryFactory, gridWidth,
gridHeight, gridCellSize);
final int count = gridWidth * gridHeight;
for (int i = 0; i < count; i++) {
writer.putInt(Integer.MIN_VALUE);
}
}
private FileChannel getFileChannel() throws IOException {
if (this.channel == null && isOpen()) {
try {
this.channel = FileChannel.open(this.path, this.openOptions, this.fileAttributes);
this.reader = new ChannelReader(this.channel);
} catch (final NoSuchFileException e) {
if (this.createMissing) {
createNewFile();
} else {
throw e;
}
}
if (!isOpen()) {
close();
return null;
}
}
return this.channel;
}
private ChannelReader getReader() throws IOException {
getFileChannel();
return this.reader;
}
public boolean isUseLocks() {
return this.useLocks;
}
@Override
protected synchronized double readElevation(final int offset) {
try {
final FileChannel fileChannel = getFileChannel();
if (fileChannel == null) {
return Double.NaN;
} else {
this.buffer.clear();
while (this.buffer.hasRemaining()) {
if (fileChannel.read(this.buffer, offset) == -1) {
return Double.NaN;
}
}
this.buffer.flip();
final int elevationInt = this.buffer.getInt();
if (elevationInt == Integer.MIN_VALUE) {
return Double.NaN;
} else {
return elevationInt / this.scaleZ;
}
}
} catch (final NoSuchFileException e) {
return Double.NaN;
} catch (final IOException e) {
throw Exceptions.wrap("Unable to read: " + this.path, e);
} finally {
this.buffer.clear();
}
}
private void readHeader() {
try {
final ChannelReader reader = getReader();
final byte[] fileTypeBytes = new byte[6];
this.reader.getBytes(fileTypeBytes);
@SuppressWarnings("unused")
final String fileType = new String(fileTypeBytes, StandardCharsets.UTF_8); // File
// type
@SuppressWarnings("unused")
final short version = this.reader.getShort();
final GeometryFactory geometryFactory = CompactBinaryGriddedElevationReader
.readGeometryFactory(this.reader, version);
final double minX = reader.getDouble();
final double minY = reader.getDouble();
final double minZ = reader.getDouble();
final double maxX = reader.getDouble();
final double maxY = reader.getDouble();
final double maxZ = reader.getDouble();
final int gridCellSize = reader.getInt(); // Grid Cell Size
final int gridWidth = reader.getInt(); // Grid Width
final int gridHeight = reader.getInt(); // Grid Height
setGeometryFactory(geometryFactory);
final BoundingBox boundingBox = geometryFactory.newBoundingBox(3, minX, minY, minZ, maxX,
maxY, maxZ);
setBoundingBox(boundingBox);
setGridCellSize(gridCellSize);
setGridWidth(gridWidth);
setGridHeight(gridHeight);
} catch (final IOException e) {
throw Exceptions.wrap("Unable to read: " + this.path, e);
}
}
public void setElevations(final double x, final double y, final double[] elevations) {
final int gridX = getGridCellX(x);
final int gridY = getGridCellY(y);
setElevations(gridX, gridY, elevations);
}
public void setElevations(final int gridX, final int gridY, final double[] elevations) {
try {
final FileChannel fileChannel = getFileChannel();
if (fileChannel != null) {
ByteBuffer buffer = this.rowBuffer;
final int gridWidth2 = getGridWidth();
if (buffer == null) {
buffer = ByteBuffer.allocateDirect(4 * gridWidth2);
this.rowBuffer = buffer;
}
final double scale = this.scaleZ;
for (final double elevation : elevations) {
final int elevationInt;
if (Double.isFinite(elevation)) {
elevationInt = (int)Math.round(elevation * scale);
} else {
elevationInt = Integer.MIN_VALUE;
}
buffer.putInt(elevationInt);
}
final int offset = this.headerSize + (gridY * gridWidth2 + gridX) * ELEVATION_BYTE_COUNT;
if (this.useLocks) {
try (
FileLock lock = fileChannel.lock(offset, elevations.length * ELEVATION_BYTE_COUNT,
false)) {
Buffers.writeAll(fileChannel, buffer, offset);
}
} else {
Buffers.writeAll(fileChannel, buffer, offset);
}
}
} catch (final IOException e) {
throw Exceptions.wrap("Unable to read: " + this.path, e);
}
}
public void setUseLocks(final boolean useLocks) {
this.useLocks = useLocks;
}
@Override
protected synchronized void writeElevation(int offset, final double elevation) {
try {
final FileChannel fileChannel = getFileChannel();
if (fileChannel != null) {
final double scale = this.scaleZ;
if (Double.isFinite(elevation)) {
final int elevationInt = (int)Math.round(elevation * scale);
this.buffer.putInt(elevationInt);
} else {
this.buffer.putInt(Integer.MIN_VALUE);
}
this.buffer.flip();
while (this.buffer.hasRemaining()) {
offset += fileChannel.write(this.buffer, offset);
}
this.buffer.clear();
}
} catch (final IOException e) {
throw Exceptions.wrap("Unable to read: " + this.path, e);
}
}
}