Register  |  Login
ThinkGeo - GPS Tracking and Mapping Solutions  |  Home  |  Cygnus Track  |   Code Community

Discussion Forums

The online community for users of Map Suite GIS components

RSS Feed Available AddThis - Bookmarking and Sharing Button Printer Friendly

PrevPrev NextNext

Sample Code: MultiGeoTiffLayer

Posted by ThinkGeo on 04-22-2009 01:59 AM

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.");
            }
        }
    }
}

2 Comments

RoseUser is Offline
04-22-2009 10:32 AM
Avatar
Hi,

Is there a VB version of this sample code for downloading?

Thx

Rose
BenUser is Offline
04-23-2009 05:15 AM
Avatar
Rose,

Here is the VB one. I did the converter using this website. You can also have a try.
http://gis.thinkgeo.com/Support/DiscussionForums/tabid/143/aff/16/aft/5641/afv/topic/Default.aspx


 Imports System
 Imports System.Collections
 Imports System.Collections.ObjectModel
 Imports System.IO
 Imports GisSharpBlog.NetTopologySuite.Geometries
 Imports 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
         Inherits Layer
         Private m_geoTiffRefrencePathFileName As String
         Private spatialIndex As STRtree
         Private m_upperScale As Double
         Private m_lowerScale As Double
         Private boundingBox As New RectangleShape()
        
         Private Const upperLeftXPosition As Integer = 0
         Private Const upperLeftYPosition As Integer = 2
         Private Const lowerRightXPosition As Integer = 1
         Private Const lowerRightYPosition As Integer = 3
         Private Const pathFileNamePosition As Integer = 4
        
         Public Sub New()
             Me.New(String.Empty, Double.MaxValue, Double.MinValue)
         End Sub
        
         Public Sub New(ByVal geoTiffRefrencePathFileName As String)
             Me.New(geoTiffRefrencePathFileName, Double.MaxValue, Double.MinValue)
         End Sub
        
         Public Sub New(ByVal geoTiffRefrencePathFileName As String, ByVal upperScale As Double, ByVal lowerScale As Double)
             Me.m_geoTiffRefrencePathFileName = geoTiffRefrencePathFileName
             Me.m_upperScale = upperScale
             Me.m_lowerScale = lowerScale
         End Sub
        
        
         Public Property GeoTiffRefrencePathFileName() As String
             Get
                 Return m_geoTiffRefrencePathFileName
             End Get
             Set(ByVal value As String)
                 m_geoTiffRefrencePathFileName = value
             End Set
         End Property
        
         Public Property UpperScale() As Double
             Get
                 Return m_upperScale
             End Get
             Set(ByVal value As Double)
                 m_upperScale = value
             End Set
         End Property
        
         Public Property LowerScale() As Double
             Get
                 Return m_lowerScale
             End Get
             Set(ByVal value As Double)
                 m_lowerScale = value
             End Set
         End Property
        
         ' 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 Overloads Overrides Sub OpenCore()
             If File.Exists(m_geoTiffRefrencePathFileName) Then
                 Dim geoTiffFiles As String() = File.ReadAllLines(m_geoTiffRefrencePathFileName)
                 spatialIndex = New STRtree(geoTiffFiles.Length)
                
                 Dim boundingBoxes As New Collection(Of BaseShape)()
                
                 For Each geoTiffLine As String In geoTiffFiles
                     Dim parts As String() = geoTiffLine.Split(New String() {","}, StringSplitOptions.None)
                     Dim geoTiffBoundingBox As New RectangleShape(New PointShape(Double.Parse(parts(upperLeftXPosition)), Double.Parse(parts(upperLeftYPosition))), New PointShape(Double.Parse(parts(lowerRightXPosition)), Double.Parse(parts(lowerRightYPosition))))
                     Dim envelope As New Envelope(Double.Parse(parts(upperLeftXPosition)), Double.Parse(parts(lowerRightXPosition)), Double.Parse(parts(upperLeftYPosition)), Double.Parse(parts(lowerRightYPosition)))
                     spatialIndex.Insert(envelope, parts(pathFileNamePosition))
                 Next
                 spatialIndex.Build()
                 boundingBox = ExtentHelper.GetBoundingBoxOfItems(boundingBoxes)
             Else
                 Throw New FileNotFoundException("The GeoTiff reference file could not be found.", m_geoTiffRefrencePathFileName)
             End If
         End Sub
        
         ' Here we set the spatial index to null to clean up the memory and get ready for serialization
         Protected Overloads Overrides Sub CloseCore()
             spatialIndex = Nothing
         End Sub
        
         ' 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 Overloads Overrides Sub DrawCore(ByVal canvas As GeoCanvas, ByVal labelsInAllLayers As Collection(Of SimpleCandidate))
             Dim currentScale As Double = ExtentHelper.GetScale(canvas.CurrentWorldExtent, canvas.Width, canvas.MapUnit)
            
             If currentScale >= m_lowerScale AndAlso currentScale <= m_upperScale Then
                 Dim currentExtent As RectangleShape = canvas.CurrentWorldExtent
                 Dim currentExtentEnvelope As New Envelope(currentExtent.UpperLeftPoint.X, currentExtent.LowerRightPoint.X, currentExtent.UpperLeftPoint.Y, currentExtent.LowerRightPoint.Y)
                 Dim geoTiffs As ArrayList = DirectCast(spatialIndex.Query(currentExtentEnvelope), ArrayList)
                
                 For Each file As String In geoTiffs
                     Dim geoTiffRasterLayer As New GeoTiffRasterLayer(file)
                     geoTiffRasterLayer.Open()
                     geoTiffRasterLayer.Draw(canvas, labelsInAllLayers)
                     geoTiffRasterLayer.Close()
                 Next
             End If
         End Sub
        
         ' Here we let everyone know we support having a bounding box
         Public Overloads Overrides ReadOnly Property HasBoundingBox() As Boolean
             Get
                 Return True
             End Get
         End Property
        
         ' We use the cached bounding box we set in the OpenCore
         Protected Overloads Overrides Function GetBoundingBoxCore() As RectangleShape
             Return boundingBox
         End Function
        
         ' This is just a handy function to build a reference file from a directory.
         ' You can tailor this code to fit your needs.
         Public Shared Sub BuildReferenceFile(ByVal newReferencepathFileName As String, ByVal pathOfGeoTiffFiles As String)
             If Directory.Exists(pathOfGeoTiffFiles) Then
                 Dim files As String() = Directory.GetFiles(pathOfGeoTiffFiles, "*.ti*")
                 Dim streamWriter As StreamWriter = Nothing
                
                 Try
                     streamWriter = File.CreateText(newReferencepathFileName)
                    
                     For Each file__1 As String In files
                         Dim geoTiffRasterLayer As New GeoTiffRasterLayer(file__1)
                         geoTiffRasterLayer.Open()
                         Dim boundingBox As RectangleShape = 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__1))
                     Next
                     streamWriter.Close()
                 Finally
                     If streamWriter IsNot Nothing Then
                         streamWriter.Dispose()
                     End If
                 End Try
             Else
                 Throw New DirectoryNotFoundException("The path containing the GeoTiff files could not be found.")
             End If
         End Sub
     End Class
 End Namespace


Ben
You are not authorized to post a reply.
Active Forums 4.2