The MultiGeoTiffLayer was inspired by a forum user who asked how he could efficiently handle working with hundreds or thousands of GeoTiffs. Up to the challenge as always, we wrote a bit of code and came up with the MultiGeoTiffLayer. While this specific class deals with GeoTiffs, you can quickly see from the code that it could be easily adapted to any kind of raster image such as MrSid or ECW.
This class allows us to build an index of all of the GeoTiff files we are going to be dealing with and then read that index when we open the Layer. The index contains the extent of the image as well as the location of the image itself. In this way, you can have one index built while the files might be split into many sub-directories. We extract the bounding boxes and place them in an in-memory R-tree spatial index that can be queried very quickly.
When the DrawCore is called during rendering, we will find out which images are in the current extent, create temporary layers for them and then pass off the drawing to them. This way we only load the images we need.
We also included a static BuildReferenceFile method that will look at all of the GeoTiff images in a directory and build up the file. You can feel free to adapt it to meet your needs. You will find the layout of the file simple and easy to replicate if you want to build up your own custom code.
The class is inherited from Layer, which means there is only one mandatory override: DrawCore. Besides that, the OpenCore is the only other one we decided to overload. By using Layer as the base class we remove many of the complications involved when inheriting from other higher level classes; however, we also lose some of the benefits. Since we simply inherit from Layer, we have no built-in support for scales. We had to add this support ourselves, but the code was easy. We also have no automatic control over transparency, RGB adjustment, etc. Having said that, we are not losing too much and I think that inheriting from Layer in this case is acceptable.
I hope you find this code helpful. If you need any assistance getting any of these optional features to work, just let us know and we can help.
The Code:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.Index.Strtree;
// IMPORTANT: You have to reference NetTopologySuite.dll & GeoApi.dll in your project. They come with Map Suite.
// This class speeds up the loading of a large number of GeoTiff layers by loading and drawing on demand only the files
// in the current extent. Normally if you had 100 GeoTiff files you would need to load 100 layers; however, this has performance
// issues--so we created this high level layer. It loads a reference file that contains the bounding box, path and file information for
// all of the GeoTiff files. We load this information into an in-memory spatial index. When the map requests to draw the layer, we find the
// GeoTiffs that are in the current extent, create a layer on-the-fly, call their Draw method and then close them. In this way, we load
// on demand only the files that are in the current extent.
// I have also included a small routine to build a reference file from a directory of GeoTiff files.
// Reference File Format: [UpperLeftPointX],[LowerRightPoint.X],[UpperLeftPoint.Y],[LowerRightPoint.Y],[Path & File Name to GeoTiff]
namespace ThinkGeo.MapSuite.Core
{
[Serializable]
public class MultiGeoTiffLayer : Layer
{
string geoTiffRefrencePathFileName;
STRtree spatialIndex;
double upperScale;
double lowerScale;
RectangleShape boundingBox = new RectangleShape();
private const int upperLeftXPosition = 0;
private const int upperLeftYPosition = 2;
private const int lowerRightXPosition = 1;
private const int lowerRightYPosition = 3;
private const int pathFileNamePosition = 4;
public MultiGeoTiffLayer()
: this(string.Empty, double.MaxValue, double.MinValue)
{ }
public MultiGeoTiffLayer(string geoTiffRefrencePathFileName)
: this(geoTiffRefrencePathFileName, double.MaxValue, double.MinValue)
{ }
public MultiGeoTiffLayer(string geoTiffRefrencePathFileName, double upperScale, double lowerScale)
{
this.geoTiffRefrencePathFileName = geoTiffRefrencePathFileName;
this.upperScale = upperScale;
this.lowerScale = lowerScale;
}
public string GeoTiffRefrencePathFileName
{
get { return geoTiffRefrencePathFileName; }
set { geoTiffRefrencePathFileName = value; }
}
public double UpperScale
{
get { return upperScale; }
set { upperScale = value; }
}
public double LowerScale
{
get { return lowerScale; }
set { lowerScale = value; }
}
// Here on the OpenCore we load our reference file and build the spatial index, which will be used in the DrawCore later.
// You need to make sure the reference file is in the right format as described in the comments above.
protected override void OpenCore()
{
if (File.Exists(geoTiffRefrencePathFileName))
{
string[] geoTiffFiles = File.ReadAllLines(geoTiffRefrencePathFileName);
spatialIndex = new STRtree(geoTiffFiles.Length);
Collection<BaseShape> boundingBoxes = new Collection<BaseShape>();
foreach (string geoTiffLine in geoTiffFiles)
{
string[] parts = geoTiffLine.Split(new string[] { "," }, StringSplitOptions.None);
RectangleShape geoTiffBoundingBox = new RectangleShape(new PointShape(double.Parse(parts[upperLeftXPosition]), double.Parse(parts[upperLeftYPosition])), new PointShape(double.Parse(parts[lowerRightXPosition]), double.Parse(parts[lowerRightYPosition])));
Envelope envelope = new Envelope(double.Parse(parts[upperLeftXPosition]), double.Parse(parts[lowerRightXPosition]), double.Parse(parts[upperLeftYPosition]), double.Parse(parts[lowerRightYPosition]));
spatialIndex.Insert(envelope, parts[pathFileNamePosition]);
}
spatialIndex.Build();
boundingBox = ExtentHelper.GetBoundingBoxOfItems(boundingBoxes);
}
else
{
throw new FileNotFoundException("The GeoTiff reference file could not be found.", geoTiffRefrencePathFileName);
}
}
// Here we set the spatial index to null to clean up the memory and get ready for serialization
protected override void CloseCore()
{
spatialIndex = null;
}
// When we get to the Draw, things are easy. First we check to make sure we are within our scales.
// Next we look up the GeoTiff files in the spatial index,
// then open their layer, call their Draw and close them.
protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
{
double currentScale = ExtentHelper.GetScale(canvas.CurrentWorldExtent, canvas.Width, canvas.MapUnit);
if (currentScale >= lowerScale && currentScale <= upperScale)
{
RectangleShape currentExtent = canvas.CurrentWorldExtent;
Envelope currentExtentEnvelope = new Envelope(currentExtent.UpperLeftPoint.X, currentExtent.LowerRightPoint.X, currentExtent.UpperLeftPoint.Y, currentExtent.LowerRightPoint.Y);
ArrayList geoTiffs = (ArrayList)spatialIndex.Query(currentExtentEnvelope);
foreach (string file in geoTiffs)
{
GeoTiffRasterLayer geoTiffRasterLayer = new GeoTiffRasterLayer(file);
geoTiffRasterLayer.Open();
geoTiffRasterLayer.Draw(canvas, labelsInAllLayers);
geoTiffRasterLayer.Close();
}
}
}
// Here we let everyone know we support having a bounding box
public override bool HasBoundingBox
{
get
{
return true;
}
}
// We use the cached bounding box we set in the OpenCore
protected override RectangleShape GetBoundingBoxCore()
{
return boundingBox;
}
// This is just a handy function to build a reference file from a directory.
// You can tailor this code to fit your needs.
public static void BuildReferenceFile(string newReferencepathFileName, string pathOfGeoTiffFiles)
{
if (Directory.Exists(pathOfGeoTiffFiles))
{
string[] files = Directory.GetFiles(pathOfGeoTiffFiles, "*.ti*");
StreamWriter streamWriter = null;
try
{
streamWriter = File.CreateText(newReferencepathFileName);
foreach (string file in files)
{
GeoTiffRasterLayer geoTiffRasterLayer = new GeoTiffRasterLayer(file);
geoTiffRasterLayer.Open();
RectangleShape boundingBox = geoTiffRasterLayer.GetBoundingBox();
geoTiffRasterLayer.Close();
streamWriter.WriteLine(string.Format("{0},{1},{2},{3},{4}", boundingBox.UpperLeftPoint.X, boundingBox.LowerRightPoint.X, boundingBox.UpperLeftPoint.Y, boundingBox.LowerRightPoint.Y, file));
}
streamWriter.Close();
}
finally
{
if (streamWriter != null) { streamWriter.Dispose(); }
}
}
else
{
throw new DirectoryNotFoundException("The path containing the GeoTiff files could not be found.");
}
}
}
}