347 строки
11 KiB
C#
347 строки
11 KiB
C#
|
using System;
|
||
|
using System.IO;
|
||
|
|
||
|
/* Jonathan Fay wrote this, except for the bits polluted by Dinoj, which are between <dinoj>...</dinoj> tags
|
||
|
* The header information in the .plate files is being upgraded.
|
||
|
* The original versiof plate files (such as those for the DSS tiles, which are of the form N177.plate etc
|
||
|
* have their first eight bytes free but empty. One had to know the number of levels they had (7 for DSS)
|
||
|
* in order to use them. This code will always have to be able to read those files for legacy purposes.
|
||
|
* The current version of .plate files has the first eight bytes used - the first four contain a magic number
|
||
|
* that says that they are this version of plate files and the next four bytes contain the number of levels.
|
||
|
* If you request a nonexistent L-X-Y tile, null is returned. Note that this sometimes happens on a level
|
||
|
* where tiles are present, since the overall images contained in the .plate file may have side length
|
||
|
* a power of 2.
|
||
|
*
|
||
|
* Example of the first eight bytes (in Hex) : 43AD697E03000000
|
||
|
*
|
||
|
* The 43AD697E is the magic number (really 7E69AD43) and the number of levels (00000003) is three.
|
||
|
* Yes, we stored them least significant byte first.
|
||
|
*/
|
||
|
|
||
|
namespace WWTWebservices
|
||
|
{
|
||
|
public class PlateTilePyramid : IDisposable
|
||
|
{
|
||
|
string filename;
|
||
|
private FileStream _readStream;
|
||
|
private bool _disposed = false;
|
||
|
|
||
|
public string FileName
|
||
|
{
|
||
|
get { return filename; }
|
||
|
}
|
||
|
int levels;
|
||
|
|
||
|
public int Levels
|
||
|
{
|
||
|
get { return levels; }
|
||
|
}
|
||
|
uint currentOffset = 0;
|
||
|
LevelInfo[] levelMap;
|
||
|
|
||
|
// <dinoj>
|
||
|
|
||
|
const uint dotPlateFileTypeNumber = 2120854851; // 7E69AD43 in hex
|
||
|
/// magic number is ceil(0.9876 * 2^31) = 0111 1110 0110 1001 1010 1101 0100 0011 in binary
|
||
|
/// this identifies that this plate file has useful header information
|
||
|
|
||
|
public PlateTilePyramid(string filename)
|
||
|
{
|
||
|
int L = -1;
|
||
|
if (numLevels(filename, out L))
|
||
|
{
|
||
|
this.filename = filename;
|
||
|
this.levels = L;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.filename = ""; // UNCLEAR WHAT TO SET THIS TO
|
||
|
this.levels = -1; // UNCLEAR WHAT TO SET THIS TO
|
||
|
}
|
||
|
}
|
||
|
// </dinoj>
|
||
|
|
||
|
public PlateTilePyramid(string filename, int levels)
|
||
|
{
|
||
|
this.filename = filename;
|
||
|
this.levels = levels;
|
||
|
}
|
||
|
|
||
|
FileStream fileStream = null;
|
||
|
|
||
|
public void Create()
|
||
|
{
|
||
|
levelMap = new LevelInfo[levels];
|
||
|
|
||
|
for (int i = 0; i < levels; i++)
|
||
|
{
|
||
|
levelMap[i] = new LevelInfo(i);
|
||
|
}
|
||
|
fileStream = File.Open(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
||
|
WriteHeaders();
|
||
|
currentOffset = HeaderSize;
|
||
|
fileStream.Seek(currentOffset, SeekOrigin.Begin);
|
||
|
|
||
|
}
|
||
|
|
||
|
// would be nice to have a version of AddFile that has as its first argument a Bitmap or Stream
|
||
|
public void AddFile(string inputFilename, int level, int x, int y)
|
||
|
{
|
||
|
long start = fileStream.Seek(0, SeekOrigin.End);
|
||
|
byte[] buf = null;
|
||
|
|
||
|
using (FileStream fs = File.Open(inputFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||
|
{
|
||
|
int len = (int)fs.Length;
|
||
|
buf = new byte[fs.Length];
|
||
|
|
||
|
levelMap[level].fileMap[x, y].start = (uint)start;
|
||
|
levelMap[level].fileMap[x, y].size = (uint)len;
|
||
|
|
||
|
fs.Read(buf, 0, len);
|
||
|
fileStream.Write(buf, 0, len);
|
||
|
fs.Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void AddStream(Stream inputStream, int level, int x, int y)
|
||
|
{
|
||
|
// closes inputStream once read
|
||
|
// added by Dinoj, debugged by Jonathan
|
||
|
// TODO: add a while loop so that it can handle very long streams
|
||
|
// (for our 256 x 256 bitmaps it's fine)
|
||
|
long start = fileStream.Seek(0, SeekOrigin.End);
|
||
|
byte[] buf = null;
|
||
|
|
||
|
int len = (int)inputStream.Length;
|
||
|
buf = new byte[inputStream.Length];
|
||
|
|
||
|
levelMap[level].fileMap[x, y].start = (uint)start;
|
||
|
levelMap[level].fileMap[x, y].size = (uint)len;
|
||
|
inputStream.Seek(0, SeekOrigin.Begin);
|
||
|
int lenRead = inputStream.Read(buf, 0, len);
|
||
|
fileStream.Write(buf, 0, len);
|
||
|
inputStream.Close();
|
||
|
}
|
||
|
|
||
|
|
||
|
public void UpdateHeaderAndClose()
|
||
|
{
|
||
|
if (fileStream != null)
|
||
|
{
|
||
|
WriteHeaders();
|
||
|
fileStream.Close();
|
||
|
fileStream = null;
|
||
|
}
|
||
|
}
|
||
|
// <dinoj>
|
||
|
static bool HasUsefulHeaders(string plateFileName)
|
||
|
{
|
||
|
// returns true if plateFileName has the magic number identifying this as a .plate file
|
||
|
int L = -1;
|
||
|
return numLevels(plateFileName, out L);
|
||
|
}
|
||
|
|
||
|
static bool numLevels(string plateFileName, out int L)
|
||
|
{
|
||
|
// Returns true if plateFileName has the magic number identifying this as a .plate file
|
||
|
// Also returns the number of levels in the .plate file.
|
||
|
// This is 10 by default i.e. if no headers are found.
|
||
|
L = 10; //
|
||
|
bool hasHeadersWithInfo = false;
|
||
|
if (File.Exists(plateFileName))
|
||
|
{
|
||
|
using (FileStream fs = new FileStream(plateFileName, FileMode.Open, FileAccess.Read))
|
||
|
{
|
||
|
if (fs != null)
|
||
|
{
|
||
|
uint FirstFourBytes, SecondFourBytes;
|
||
|
FirstFourBytes = GetNodeInfo(fs, 0, out SecondFourBytes);
|
||
|
if (FirstFourBytes == dotPlateFileTypeNumber)
|
||
|
{
|
||
|
L = (int)SecondFourBytes;
|
||
|
hasHeadersWithInfo = true;
|
||
|
}
|
||
|
}
|
||
|
fs.Close();
|
||
|
}
|
||
|
}
|
||
|
return hasHeadersWithInfo;
|
||
|
}
|
||
|
// </dinoj>
|
||
|
|
||
|
private void WriteHeaders()
|
||
|
{
|
||
|
// <dinoj>
|
||
|
uint L = (uint)levels;
|
||
|
byte[] buffer = new byte[8];
|
||
|
buffer[0] = (byte)(dotPlateFileTypeNumber % 256);
|
||
|
buffer[1] = (byte)((dotPlateFileTypeNumber >> 8) % 256);
|
||
|
buffer[2] = (byte)((dotPlateFileTypeNumber >> 16) % 256);
|
||
|
buffer[3] = (byte)((dotPlateFileTypeNumber >> 24) % 256);
|
||
|
buffer[4] = (byte)L;
|
||
|
buffer[5] = (byte)(L >> 8);
|
||
|
buffer[6] = (byte)(L >> 16);
|
||
|
buffer[7] = (byte)(L >> 24);
|
||
|
|
||
|
fileStream.Write(buffer, 0, 8);
|
||
|
// </dinoj>
|
||
|
|
||
|
uint currentSeek = 8;
|
||
|
foreach (LevelInfo li in levelMap)
|
||
|
{
|
||
|
int count = (int)Math.Pow(2, li.level);
|
||
|
for (int y = 0; y < count; y++)
|
||
|
{
|
||
|
for (int x = 0; x < count; x++)
|
||
|
{
|
||
|
SetNodeInfo(fileStream, currentSeek, li.fileMap[x, y].start, li.fileMap[x, y].size);
|
||
|
currentSeek += 8;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint HeaderSize
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return GetFileIndexOffset(levels, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static public uint GetFileIndexOffset(int level, int x, int y)
|
||
|
{
|
||
|
uint offset = 8;
|
||
|
for (uint i = 0; i < level; i++)
|
||
|
{
|
||
|
offset += (uint)(Math.Pow(2, i * 2) * 8);
|
||
|
}
|
||
|
|
||
|
offset += (uint)(y * Math.Pow(2, level) + x) * 8;
|
||
|
|
||
|
return offset;
|
||
|
|
||
|
}
|
||
|
|
||
|
public Stream GetFileStream(int level, int x, int y)
|
||
|
{
|
||
|
if (filename.Length > 0 && File.Exists(filename) && levels > level)
|
||
|
{
|
||
|
return GetFileStream(filename, level, x, y);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
static public Stream GetFileStream(string filename, int level, int x, int y)
|
||
|
{
|
||
|
uint offset = GetFileIndexOffset(level, x, y);
|
||
|
uint length;
|
||
|
uint start;
|
||
|
|
||
|
MemoryStream ms = null;
|
||
|
using (FileStream f = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||
|
{
|
||
|
f.Seek(offset, SeekOrigin.Begin);
|
||
|
start = GetNodeInfo(f, offset, out length);
|
||
|
|
||
|
byte[] buffer = new byte[length];
|
||
|
f.Seek(start, SeekOrigin.Begin);
|
||
|
f.Read(buffer, 0, (int)length);
|
||
|
ms = new MemoryStream(buffer);
|
||
|
f.Close();
|
||
|
}
|
||
|
return ms;
|
||
|
}
|
||
|
/*
|
||
|
|
||
|
public byte[] GetTile(int level, int x, int y)
|
||
|
{
|
||
|
uint offset = GetFileIndexOffset(level, x, y);
|
||
|
uint length;
|
||
|
uint start;
|
||
|
|
||
|
MemoryStream ms = null;
|
||
|
if (_readStream == null)
|
||
|
_readStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||
|
_readStream.Seek(offset, SeekOrigin.Begin);
|
||
|
start = GetNodeInfo(_readStream, offset, out length);
|
||
|
if (length == 0)
|
||
|
return null;
|
||
|
byte[] buffer = new byte[length];
|
||
|
_readStream.Seek(start, SeekOrigin.Begin);
|
||
|
_readStream.Read(buffer, 0, (int)length);
|
||
|
return buffer;
|
||
|
}
|
||
|
*/
|
||
|
static public uint GetNodeInfo(FileStream fs, uint offset, out uint length)
|
||
|
{
|
||
|
Byte[] buf = new Byte[8];
|
||
|
fs.Seek(offset, SeekOrigin.Begin);
|
||
|
fs.Read(buf, 0, 8);
|
||
|
|
||
|
length = (uint)(buf[4] + (buf[5] << 8) + (buf[6] << 16) + (buf[7] << 24));
|
||
|
|
||
|
return (uint)((buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24)));
|
||
|
}
|
||
|
|
||
|
static public void SetNodeInfo(FileStream fs, uint offset, uint start, uint length)
|
||
|
{
|
||
|
Byte[] buf = new Byte[8];
|
||
|
buf[0] = (byte)start;
|
||
|
buf[1] = (byte)(start >> 8);
|
||
|
buf[2] = (byte)(start >> 16);
|
||
|
buf[3] = (byte)(start >> 24);
|
||
|
buf[4] = (byte)length;
|
||
|
buf[5] = (byte)(length >> 8);
|
||
|
buf[6] = (byte)(length >> 16);
|
||
|
buf[7] = (byte)(length >> 24);
|
||
|
|
||
|
fs.Seek(offset, SeekOrigin.Begin);
|
||
|
fs.Write(buf, 0, 8);
|
||
|
}
|
||
|
public void Dispose()
|
||
|
{
|
||
|
GC.SuppressFinalize(this);
|
||
|
Dispose(true);
|
||
|
}
|
||
|
public void Dispose(bool disposing)
|
||
|
{
|
||
|
if (!this._disposed)
|
||
|
{
|
||
|
// If disposing equals true, dispose all managed and unmanaged resources.
|
||
|
if (disposing)
|
||
|
{
|
||
|
if (_readStream != null)
|
||
|
_readStream.Dispose();
|
||
|
_readStream = null;
|
||
|
}
|
||
|
this._disposed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public class LevelInfo
|
||
|
{
|
||
|
public int level;
|
||
|
public NodeInfo[,] fileMap;
|
||
|
|
||
|
public LevelInfo(int level)
|
||
|
{
|
||
|
this.level = level;
|
||
|
fileMap = new NodeInfo[(int)Math.Pow(2, level), (int)Math.Pow(2, level)];
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public struct NodeInfo
|
||
|
{
|
||
|
public uint start;
|
||
|
public uint size;
|
||
|
public string filename;
|
||
|
}
|
||
|
}
|