ThinkGeo.com    |     Documentation    |     Premium Support

Rasters and Tile Indexing

Hello everyone,



I need to manage very large raster layers (ecw format).

I splitted the raster image into a number of smaller images.

Each image is about 20 MB (extent: 3800 x 3100 meter); the number of images is greater than 1000.

Each file is a tile of the larger raster mosaic available for display.



I want to implement the same technique of MapServer: Tile Indexing.

In brief:

"The list of files forming a layer can be stored in a shapefile with polygons representing the footprint of each file, and the name of the files. 

This is called a TILEINDEX and works similarly to the same feature in vector layers. 

The result can be represented in the MAP file as one layer, but MapServer will first  scan the tile index, and ensure that only raster files overlapping the current display  request will be opened. .... "


See: mapserver.org/input/raster.html


Well, I want something similar. 



Before I began writing the code, 

I'd like to know if the Map Suite Web Edition already offer native of similar mechanisms (I could not find a developer guide).

I'd like to know also thought someone has already addressed a similar problem.



I read the post 

gis.thinkgeo.com/Support/Discussion...fault.aspx

But, I want to do something different.



Many thanks,

Michele B.



Michele, 
  
   I think I can help you with that.  We have a code community project that handles something very similar.  See the link below.  In this project we create a single layer the encapsulates all of the raster files.  First we run a static method to build up a text file index of the file names and bounding boxes of all the similar files in a directory.  Once we have that file we load it up in an in-memory spatial index file that we use to load the images on demand depending if the bounding box intersects a certain tile. 
  
   I think in general the above is what you want.  The code is all provided so you can tweak it as you see fit.  The specific example handles JPEG files but it can easily be modified to handle ECW files.  It could also be modified to load the bounding boxes on demand and skip the step of building the text file index.  If the files will be constant the index file will make it faster as you only need to open and read only file versus multiple ECW files.  Take a quick look at the code provided in the code community and see if this is what you are looking for.  I would be happy to modify it up to meet your need if you let me know if this is close.  If it is not what you were thinking let me know and we can figure something else out for you. 
  
 code.thinkgeo.com/projects/show/multigeorasterlayer 
  
 David 


Michele,



  I did a quick review of the code and changed the few places that were needed to make it work for ECW.  Hope this saves you a few minutes.  The changes are posted below.  Please feel free to review the original code, or checkout all the cool code community project, and ask any question if you need clarification.



David







using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.Index.Strtree;
using ThinkGeo.MapSuite.Core;

namespace MultiGeoRasterLayer
{
    // 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 Raster layers by loading and drawing on demand only the files
    // in the current extent.  Normally if you had 100 Raster 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 Raster files.  We load this information into an in-memory spatial index. When the map requests to draw the layer, we find the
    // Rasters 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 Raster files.

    // Reference File Format: [UpperLeftPointX],[LowerRightPoint.X],[UpperLeftPoint.Y],[LowerRightPoint.Y],[Path & File Name to Raster]    
    
    // Sample:
    // -180.02197265625,-157.52197265625,-67.47802734375,-89.97802734375,..\..\App_Data\RasterImage\1.jpg
    // -112.52197265625,-90.02197265625,-67.47802734375,-89.97802734375,..\..\App_Data\RasterImage\10.jpg
    // 67.47802734375,89.97802734375,67.52197265625,45.02197265625,..\..\App_Data\RasterImage\100.jpg
    // 89.97802734375,112.47802734375,67.52197265625,45.02197265625,..\..\App_Data\RasterImage\101.jpg

    public class MultiGeoRasterLayer : Layer
    {
        private string rasterRefrencePathFileName;
        private STRtree spatialIndex;
        private double upperScale;
        private double lowerScale;
        private RectangleShape boundingBox;

        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 MultiGeoRasterLayer()
            : this(string.Empty, double.MaxValue, double.MinValue)
        { }

        public MultiGeoRasterLayer(string rasterRefrencePathFileName)
            : this(rasterRefrencePathFileName, double.MaxValue, double.MinValue)
        { }

        public MultiGeoRasterLayer(string rasterRefrencePathFileName, double upperScale, double lowerScale)
        {
            this.rasterRefrencePathFileName = rasterRefrencePathFileName;
            this.upperScale = upperScale;
            this.lowerScale = lowerScale;
            boundingBox = new RectangleShape();
        }

        public string RasterRefrencePathFileName
        {
            get { return rasterRefrencePathFileName; }
            set { rasterRefrencePathFileName = 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(rasterRefrencePathFileName))
            {
                string[] rasterFiles = File.ReadAllLines(rasterRefrencePathFileName);
                spatialIndex = new STRtree(rasterFiles.Length);

                Collection<BaseShape> boundingBoxes = new Collection<BaseShape>();

                foreach (string rasterLine in rasterFiles)
                {
                    string[] parts = rasterLine.Split(new string[] { "," }, StringSplitOptions.None);
                    RectangleShape rasterBoundingBox = 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 Raster reference file could not be found.", rasterRefrencePathFileName);
            }
        }

        // 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 Raster 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 rasters = (ArrayList)spatialIndex.Query(currentExtentEnvelope);

                foreach (string file in rasters)
                {
                    EcwRasterLayer rasterRasterLayer = new EcwRasterLayer(file);
                    rasterRasterLayer.Open();
                    rasterRasterLayer.Draw(canvas, labelsInAllLayers);
                    rasterRasterLayer.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 pathOfRasterFiles)
        {
            if (Directory.Exists(pathOfRasterFiles))
            {
                string[] files = Directory.GetFiles(pathOfRasterFiles, "*.ecw");
                StreamWriter streamWriter = null;

                try
                {
                    streamWriter = File.CreateText(newReferencepathFileName);

                    foreach (string file in files)
                    {
                        EcwRasterLayer rasterRasterLayer = new EcwRasterLayer(file);
                        rasterRasterLayer.Open();
                        RectangleShape boundingBox = rasterRasterLayer.GetBoundingBox();
                        rasterRasterLayer.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 Raster files could not be found.");
            }
        }
    }
}
 



I think it is an excellent starting point.

I start work immediately,



PS.

I have not found a developer guide of Map Suite Solution (Desktop, Web, ... ),  there is one?


Thank you very much.


Michele B.



Michele, 
  
 Yes, this project in the code community is for service edition. While our Desktop, Web and Silverlight are based on the service edition. You can simply copy the code above to your solution and use it like the layers you used to use. 
  
 Hope it makes sense and let us know if you have any more queries. 
  
 Thanks, 
 Howard