ThinkGeo.com    |     Documentation    |     Premium Support

Register additional tif files when panning is finished

I'm using a lot of tif files (with worldfiles) for several scales e.g. 1:000000, 1:50000, 1:25000, ... In total I have around 1200 different files. Registering them all at once take too much time (or can fail). Now I would like to know if there is an event which signals that panning the map has been finished (mouse click - mouse move - mouse release). The idea is to determine on the current scale and the coordinates which maps (files) needs to be registered. Or is there an alternate solution lots of files?


Thanks in advance



Klaus, 
  
   This is an interesting issue, I think we have a great solution for you but it might require a little leg work.  Let’s say for instance that you have three main scales worth of data and for each scale you have a few hundred tiles, something similar to what you mentioned.  As you said loading each of the images would take too much time as there is overhead in each layer that you add even it it is small having thousand of them is noticeable.  I think the best solution would be to to create your own RasterSource and RasterLayer to aggregate all of the file together and then load just the tiles you need on demand.  I think we could build this for you but I want to explain the premise for you and other out these thinking about tis same kind of problem. 
  
    I would first create a new RasterSource from scratch that would take in a directory and maybe a file matching pattern.  This would allow me to know what tiles are associated with this layer.  Next on the open I would OpenCore the world files and read into a in memory dictionary of all of the bounding boxes for the various tiles.  In the DrawCore what I would do is loop through the bounding boxes in memory and find the tiles that are needed for the extent I wanted.  I would then create inside of the DrawCore a temporary GeoTiffRasterSource passing in the single file I wanted to draw and then proxy the call off to its DrawCore.  I would do this in a little loop for all of the tiles I needed to draw at this extent.  After using each tile I would close it or maybe cache it depending on how fancy I wanted to get.  In the CloseCore I would clear the cache of bounding boxes and any other caches I had.  As you can see in this way we create a thin wrapper RasterLayer and RasterSource that will load the files needed on demand.  I hope this makes sense and if this is something that you could use let me know and we can get you a sample and post it over at the developers blog. 
  
 I will also put in a request to have a webinar on how to create custom RasterSource and RasterLayer classes similar to the other webinars we did.  If you havent gotten a chance please watch the free video on creating you own FeatureSource and FeatureLayer, the Raster stuff is very similar. 
  
 I highly recommend watching the video if you have the time. 
  
 gis.thinkgeo.com/Support/DiscussionForums/tabid/143/aff/16/aft/4769/afv/topic/Default.aspx 
  
 David

David, I was starting down this path for our application as well.   We have a drive with a folder for every county in the country, and in that folder, we have a Mr SID image and five shapefiles (roads, creeks, lakes, etc).   I only have this started, but now I have a few questions, based upon your post: 
  
 1)  I don’t find a DrawCore method on the RasterSource.   I’m assuming you left out the step of creating a RasterLayer or an Overlay, and having that iterate the tiles, etc? 
  
 2) I created a FeatureSource to access the data, but it’s pretty light weight. 
  
 3) I was planning on having my custom overlay iterate the tiles, drawing all of the Mr SID images, then drawing all of the lake shapefiles, creek shapefiles, road shapefiles, etc, etc.    Is there any reason that I cannot draw both raster and vector layers within my single custom layer or custom overlay. 
  
 4) I had started building a custom overlay to do this.   However, I just looked at the object browser, and I see that the overlay is defined within the DesktopEdition.  I want this implementation to be available when I switch to the services edition for our background map generator.   I have not installed the services edition, but is the Overlay concept specific to the desktop edition?   If so, I should build this into a custom layer, rather than a custom overlay? 
  
 Thanks for your comments here.  This is a very good discussion.

Ted, 
  
   You are correct in that the RasterSource does not have a DrawCore, it has something like a GetImageCore instead because it isn’t really doing any drawing just fetching an image. 
  
   There isn’t anything stopping you from using FeatureSource for your plan. I think it is somewhat fitting since you do have vector data as well.  If I were to only have raster data then a FeatureSource would be pointless since the spatial query features etc would always return back nothing.   
  
   You are also correct that if you want to share your stuff between the Desktop and Services Editions then you better create a custom layer and stay away from Overlay.  Overlays are product specific and allow us to do things specific to certain environments like markers in the web edition versus real-time GoogleMapsOverlay that is specific to the Desktop.  I suggest you inherit from FeatureSource and FeatureLayer and then for the vector stuff you can create a MrSidRasterLayer inside of the FeatureLayer and draw the image on the DrawCore of it.  You can also inherit from Layer itself but you miss out on a few things like QueryTools etc but you may or may not need them. 
  
 David

David


I just red your first post. In fact I'm already doing something like you suggested. I've created a catalog (csv file) where I have the information of the coordinates from each tile,. I load this catalog at application startup. While panning around I use the OverlayDrawn event to determine and load missing tiles depending on this catalog. Your solution makes sense to me and might be better and would help other developers. So please post your sample and at the developers blog.


Thanks Klaus



Klaus,



  Here is the code I whipped up.  It uses a reference file and I included a static method to build it.  Feel free to enhance this anyway you need to.  The code is pretty simple and straight forward.  Let me know if you have any questions.  Also make sure to read the comments in the code, it explains everything.



David

 


 




using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.IO;
using System.Collections.Generic;
using GisSharpBlog.NetTopologySuite.Index.Strtree;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite;
using System.Collections;
using ThinkGeo.MapSuite.Core;

    //IMPORTANT: You have to reference NetTopologySuite.dll & GeoApi.dll in your project.  They come with Map Suite.

    // This class speeds up the loading of large number of GeoTiff layers by loading and drawing on deman just 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 refrence file that contains the bounding box and path and file infromation for all of the
    // GeoTiff files.  We load this infromation into an in memeory spatial index. When the map requests to draw the layer we find the
    // GeoTiff's that are in the current extent and create a layer on the fly and call their Draw method and close them.  In this way we load
    // on demand just the files that are in the current extent.
    // I have also included a small routine to build a refrence file from a directory of GeoTiff files.
       
    // Reference File Format: [UpperLeftPointX],[LowerRightPoint.X],[UpperLeftPoint.Y],[LowerRightPoint.Y],[Path & File Name to GeoTiff]    
    public class MultiGeoTiffLayer : Layer
    {
        string geoTiffRefrencePathFileName;
        STRtree spatialIndex;

    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)
        {}

        public MultiGeoTiffLayer(string geoTiffRefrencePathFileName)
        {
            this.geoTiffRefrencePathFileName = geoTiffRefrencePathFileName;
        }

        public string GeoTiffRefrencePathFileName
        {
            get { return geoTiffRefrencePathFileName; }
            set { geoTiffRefrencePathFileName = value; }
        }

        // Here on the OpenCore we load our reference file and build the spatial index for use int he DrawCore later.
        // You need to make sure the reference file is in the right format as described above.
        protected override void OpenCore()
        {
            if (File.Exists(geoTiffRefrencePathFileName))
            {
                string[] geoTiffFiles = File.ReadAllLines(geoTiffRefrencePathFileName);
                spatialIndex = new STRtree(geoTiffFiles.Length);

                foreach (string geoTiffLine in geoTiffFiles)
                {
                    string[] parts = geoTiffLine.Split(new string[] { "," }, StringSplitOptions.None);
                    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();                
            }
            else
            {
                throw new FileNotFoundException("The GeoTiff reference file could not be found.", geoTiffRefrencePathFileName);
            }
        }

        // When we get to the Draw things are easy.  We lookup the GeoTiff files in the spatial index
        // and then open their layer and call their Draw and close them.
        protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            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();
            }
        }

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


I haven’t implemented this… just verified that it compiled for me if I wanted to experiment.   However, in reviewing the code, I’m left with two questions: 
  
 1) Why do you build the spatial tree every time the layer is opened?   The layer gets opened every time there is any screen movement, right?   In my implementation (also in process and untested), I built the index list when the feature source was instantiated.  Will I have an issue with that? 
  
 2) How do you get by w/o having a FeatureSource?   When I have a layer that I don’t add a FeatureSource to, and then try to use it, I get that error that is way down inside MapSuite (referenced in another thread).    Ohhhhhhhh… you inherit from Layer, rather than FeatureLayer.   Hmmmm.  Does that mean that a FeatureSource is only required for a FeatureLayer? 
  
 I learn a lot from your examples.   Thank you for doing them.    I am not familar with the NTS spatial indexing at all.

Ted, 
  
 1.The number of times we open and close a layer depends on if we use it in the Desktop or Web Editions.  In the Desktop Edition we open the layers only once and then keep it open the entire life of the control unless you close it yourself.  We do this because there is no need to close it and it gives us some speed to not have to open and close it.   
  
 In the Web Edition it is much different because we do not want to keep feature sources open between requests to lower the resource demands on the server and we never know when a request is the last one from the client.  Typical disconnected client problems. We also support things like SQL Server session state and State Server so for them to work we need to close the layer so the objects can be serialized.  We have thought about keeping the objects live to get a little more speed but as of yet not been able to work out all the problems in a way that satisfies everyone. 
  
 As a general rule we want people to, on the Close, prepare for serialization which generally means to set all non serialization objects to null, throw away any larges caches that you do not want to travel with the XML etc.  We want objects to be able to be opened and closed many times and keep a viable working state. 
  
   If I were to make this class specifically for the web I might keep the cache alive and then in the open just build it if it didn’t already exist.  This is where you need a good plan when thinking about building for the Web and Desktop.  Since this way a Desktop forum post I just took some shortcuts. :-) 
  
 2.You are exactly right, I inherited from one more level up to keep from having the feature or raster source as a dependency.  You are correct that FeatureLayer needs a FeatureSource.  The reason we built the FeatureLayer is that since we know we have a FeatureSource we can build in the QueryTools, EditTools, ZoomLevels and a bunch of stuff you want on every Layer that is made up of Features.  This gives the developer allot of free functionality.  For raster stuff we have a RasterSource and a RasterLayer that are similar to FeatureSource and FeatureLayer. 
  
 I did make a trade off by inheriting from Layer which is I have very little functionality by default.  Layer is pretty sparse but quick to build from.  For example in my sample the GeoTiffs are displayed at all scales.  If I wanted to limit the scales it is shown at I have two options.  First I could add an upper and lower scale property and handle that myself in the DrawCore.  I could also inherit from RasterLayer and have a RasterSource which has some nice scale logic built in it.  I didn’t do this because RasterSource returns an image from it’s GetImageCore and it was easier for me to just leverage the Draw method of the layer and skip compositing the images. For this sample I really went cheap because I wanted to bang out something fast.  If I were building this class to include in our Core framework I would have done things quite differently. 
  
 3.I love building samples and extending the platform.  The quick ones like this are really nice because you can see the power in just a few lines of code.  I am also so happy when I see how good design decisions on our inheritance model pay off in the ability quickly extend the classes.  
 Building classes for the Core name-space are a bit more difficult as you can see from above as there is allot to think about.  We need usage scenarios, parameter validation, test cases, documentation, error message review, code review, FX Cop review, Style Cop review, architect review, public surface review, dependency review and I am sure I am forgetting something.  It is enough to take the fun out of coding.  
  
 We will soon be announcing a new code site for the Map Suite community.  I think it will be code.thinkgeo.com or something like that.  The site will have a CodePlex feel and we will be hosting a number of shared source extensions and also allow developers to share their own extensions and host their own communities.  The site uses subversion as it’s code repository and is pretty slick.  We need to get these samples out and have a nice place where people can show off what they are building and how they do it. 
  
  
 David

This has been a really help discussion for me, David.  You’ve pointed out several little things that will make a decision in our core framework.   I know that because I’m replying, that a ThinkGeo person will have to reply back, and I’m sorry for that, but I really wanted for you to know how helpful this kind of information is, at a key time for us.

Ted, 
  
   It is my pleasure.  I enjoy answering questions from insightful people.  I don’t mind if you reply, it just gets my total message count up and maybe one day someone will notice that and give me a bonus. 
  
 David

David




Thanks for posting your code sample. I walked through the code and I think also from the following posts with Ted that this approach will help us to develop our application.


Klaus



Klaus, 
  
   Glad the code could help.  There is allot of flexibility in the framework but the price is a learning curve on how it could be used.  We are thinking up some ways to better communicate how to use the APIs in more extreme cases. 
  
 David