ThinkGeo.com    |     Documentation    |     Premium Support

Projection Issue with GetBitmap method

 I am having issues trying to capture and save the map image using the GetBitmap method.



The image which is displayed in the browser has the correct projection, however when I use the GetBitmap method and save the image out to a file, the projection of the image is not correct.


 


I have the full lic version of ThinkGeo MVC edition, the water marks in the image are due to me using my developer computer and iis express 8.


I downloaded the latest dll’s from our customer portal. The versions are as follows.


MapSuiteCore.dll             6.0.0.198


MvcEdition.dll                   6.0.0.198



 


 


Image from the browser which is using correct projection



 


Saved image from GetBitmap using incorrect projection 



This is the gist of the code I am using


 


 


 





public imageOutput SaveImageCommand( imageInput input )
        {
            Map map = GetMapFromRequest( input.MapSessionId );
            var MyBitmap = map.GetBitmap( );
            MyBitmap.Save( @"d:\testmapImage2.png", ImageFormat.Png );
            return new imageOutput( );

        }


public void CreateMap(){  
  Map map = new Map( MapName, new Unit( 100, UnitType.Percentage ), new Unit( 100, UnitType.Percentage ) );
     map.MapBackground.BackgroundBrush = (new GeoSolidBrush( GeoColor.SimpleColors.Transparent ));
            map.MapUnit = GeographyUnit.Meter;

 LayerOverlay featuresOverlay = new LayerOverlay( "features" );
          
            featuresOverlay.IsVisible = true;
          
            featuresOverlay.IsBaseOverlay = false;
            featuresOverlay.ServerCache.CacheDirectory = _settings.TileCachePath;
            featuresOverlay.ServerCache.CacheId = Guid.NewGuid( ).ToString( );
            featuresOverlay.Name = "features";
            featuresOverlay.WebImageFormat = WebImageFormat.Png;

            featuresOverlay.ClientCache.CacheId = "clientCacheID";

            featuresOverlay.ClientCache.Duration = new TimeSpan( 0, 10, 0 );

  featureOverlay.Layers.Add( ProcessFeatureShapes(input.ShapeData, projSphericalMercator) );

     map.CustomOverlays.Add( featuresOverlay );
   map.CurrentExtent = GetFullExtent( featureOverlay.Layers );
 }
 
 
 
 private RectangleShape GetFullExtent( GeoCollection<Layer> Layers )
        {
            List<BaseShape> rectangleShapes = new List<BaseShape>( );

            foreach ( Layer layer in Layers )
            {
                layer.Open( );

                if ( layer.HasBoundingBox == true )
                    rectangleShapes.Add( layer.GetBoundingBox( ) );
            }

            return ExtentHelper.GetBoundingBoxOfItems( rectangleShapes );
        }  
 
private InMemoryFeatureLayer ProcessFeatureShapes( HashSet<ShapeData> shapeDataCollection, Proj4Projection projSphericalMercator )
        {
            InMemoryFeatureLayer layer = new InMemoryFeatureLayer( );

            layer.Open( );
            layer.Columns.Add( new FeatureSourceColumn( "name", DbfColumnType.String.ToString( ), 100 ) );
            layer.Columns.Add( new FeatureSourceColumn( "data", DbfColumnType.Double.ToString( ), 40 ) );

            
            layer.EditTools.BeginTransaction( );
            

            foreach ( var shapeData in shapeDataCollection )
            {

                var data = _shapeFileProvider.GetShapeFileBinaryData( shapeData.Geography.GeographyType, shapeData.ShapeFileName ); //get the well known binary data

                Feature feature = new Feature( data );

                feature.ColumnValues.Add( "name", shapeData.Name );

                feature.ColumnValues.Add( "data", shapeData.DataValue.ToString( ) );

                feature.GetBoundingBox( ); //force the calculation.

                layer.EditTools.Add( feature );


            }
            
            layer.EditTools.CommitTransaction( );

            layer.BuildIndex( );

            layer.FeatureSource.Projection = projSphericalMercator;

            layer.Close( );

            return layer;
        }


Hi Rick, 
  
 As your code is not runnable, so we tried to use our sample to recreate your scenario, but the image we get is the same as that in the browser, could you please provide a simple sample that could run? 
  
 Thanks, 
 Edgar

 I took the example code "CSharp HowDoISamples Razor" and modified the UseADifferentProjectionForAFeatureLayer example to demostrate the issue.


I suspect, by the getbitmap call is not respecting the projection the layer is using, but instead using the orginal projection from the shape file.


when the image saves to disk, it is blank.


Here is the modified code for the view and controller.



I had to remove the < from the script tag so the text would show.

 



@using ThinkGeo.MapSuite.Core;
@using ThinkGeo.MapSuite.MvcEdition;

script language="javascript" type="text/javascript">
    function mapClick(e) {
        Map1.ajaxCallAction('@ViewContext.RouteData.Values["Controller"].ToString( )', 'ClickEvent', { x: e.worldXY.lon, y: e.worldXY.lat }, null);
    }

/script>

    @{Html.RenderPartial( "SourceCode" );}
    
        
            This sample converts a layer's projection from EPSG:4326 to EPSG:2163.
            

            <input type="button" id="btnZoomIn" value="Zoom In" onclick="Map1.zoomIn();return false;" />
            <input type="button" id="btnZoomOut" value="Zoom Out" onclick="Map1.zoomOut();return false;" />
        
    


    
        @{
            Html.ThinkGeo( ).Map( "Map1", new System.Web.UI.WebControls.Unit( 100, System.Web.UI.WebControls.UnitType.Percentage ), 510 )
                .MapBackground( new BackgroundLayer( new GeoSolidBrush( GeoColor.FromHtml( "#E5E3DF" ) ) ) )
                .CurrentExtent( -19268509.29874, 13535292.38285, 20656089.34576, -14435365.7673 )
                .MapUnit( GeographyUnit.Meter )
                .StaticOverlay( staticOverlay =>
                {
                    Proj4Projection proj4Projection = new Proj4Projection( );
                    proj4Projection.InternalProjectionParametersString = Proj4Projection.GetEpsgParametersString( 4326 );
                    proj4Projection.ExternalProjectionParametersString = Proj4Projection.GetGoogleMapParametersString( );

                    ShapeFileFeatureLayer worldLayer = new ShapeFileFeatureLayer( Server.MapPath( "~/App_Data/cntry02.shp" ) );
                    worldLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle( GeoColor.FromArgb( 255, 243, 239, 228 ), GeoColor.FromArgb( 255, 218, 193, 163 ), 1 );
                    worldLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
                    worldLayer.FeatureSource.Projection = proj4Projection;

                    staticOverlay.TileType( TileType.SingleTile ).Layer( "WorldLayer", worldLayer );
                } )
                .OnClientClick( "mapClick" )
                .Render( );
        }
    


  
using System.Web.Mvc;
using ThinkGeo.MapSuite.MvcEdition;
using ThinkGeo.MapSuite.Core;
using System.Drawing.Imaging;

namespace CSharp_HowDoISamples
{
    public partial class ProjectionController : Controller
    {
        //
        // GET: /UseADifferentProjectionForAFeatureLayer/

        public ActionResult UseADifferentProjectionForAFeatureLayer( )
        {
            return View( );
        }

        [MapActionFilter]
        public void ClickEvent( Map map, GeoCollection<object> args )
        {
           
            var MyBitmap = map.GetBitmap( );
            MyBitmap.Save( @"d:\testmapImage2.png", ImageFormat.Png );
        }

    }

  
}



Rick, 
  
 Please get the 6.0.202.0 or 6.0.0.202 or later and have a try, we fixed the bug in that version. 
  
 Regards, 
 Edgar

 I tested with the updated version 6.0.0.202. and the UseADifferentProjectionForAFeatureLayer  does now save the correct image.


I also tested with the version 6.0.0.209


however, when i applied it to my application, i am still getting a different projection on the saved image.


I am using an InMemoryFeatureLayer and reading an external file (wellknownbinary data) and creating a feature from that and adding it into the layer.


I modified the LoadAHeatLayer example in the HowDoISamples razor. which basically does what our application is doing, and am able to reproduce the exact issue.


I have included the modified source, and the binaryfile i used.


The binary file (48.imgData) , i put into the App_data folder. The file is the representation of the State of Texas. I had to zip the file so I could attach it to the post.


View Code:


I had to remove the < from the script tag so the text would show.


 


 



@using ThinkGeo.MapSuite.MvcEdition
@using ThinkGeo.MapSuite.Core
@model ThinkGeo.MapSuite.MvcEdition.Map 
script language="javascript" type="text/javascript">
    function mapClick(e) {
        var params = { x: e.worldXY.lon, y: e.worldXY.lat };

        Map1.ajaxCallAction('@ViewContext.RouteData.Values["Controller"].ToString( )', 'ClickEvent', params, mapCallback);
    }

    function mapCallback(result) {
        Map1.redrawLayer('features');
    }
/script>

    @{Html.RenderPartial( "SourceCode" );}
    
        
            This sample demonstrates how to show HeatLayer with MapSuite.
        
    


    
        @{
            Html.ThinkGeo( ).Map( Model )
                .OnClientClick( "mapClick" ).Render( );
        }
    


Controller Code:


 



using System;
using System.Web.Mvc;
using ThinkGeo.MapSuite.Core;
using ThinkGeo.MapSuite.MvcEdition;
using System.Drawing.Imaging;
using System.IO;
using System.Collections.Generic;

namespace CSharp_HowDoISamples
{
    public partial class LayersFeatureSourcesController : Controller
    {
        //
        // GET: /LoadAHeatLayer/

        public ActionResult LoadAHeatLayer( )
        {
            Map map = new Map( "Map1", new System.Web.UI.WebControls.Unit( 100, System.Web.UI.WebControls.UnitType.Percentage ), new System.Web.UI.WebControls.Unit( 100, System.Web.UI.WebControls.UnitType.Percentage ) );
            map.MapBackground.BackgroundBrush = (new GeoSolidBrush( GeoColor.SimpleColors.Transparent ));

            map.MapUnit = GeographyUnit.Meter;
            map.StaticOverlay.SetBaseEpsgProjection( Proj4Projection.GetGoogleMapParametersString( ) );

            Proj4Projection projSphericalMercator = new Proj4Projection( );
            projSphericalMercator.InternalProjectionParametersString = Proj4Projection.GetEsriParametersString( 4326 ); //North America Albers Equal Area Conic
            projSphericalMercator.ExternalProjectionParametersString = Proj4Projection.GetGoogleMapParametersString( );

            projSphericalMercator.Open( );
            var layer = GetFeatureLayer( );
            layer.FeatureSource.Projection = projSphericalMercator;

            LayerOverlay featuresOverlay = new LayerOverlay( "features" );
            featuresOverlay.IsVisible = true;
            featuresOverlay.Name = "features";
            featuresOverlay.WebImageFormat = WebImageFormat.Png;
            featuresOverlay.Layers.Add( layer );

            map.StaticOverlay.IsBaseOverlay = false;
            map.CustomOverlays.Add( featuresOverlay );
            featuresOverlay.IsBaseOverlay = true;
            featuresOverlay.SetBaseEpsgProjection( Proj4Projection.GetGoogleMapParametersString( ) );
            featuresOverlay.IsBaseOverlay = false;


            map.CurrentExtent = GetFullExtent( featuresOverlay.Layers );

            return View( map );
        }


        public InMemoryFeatureLayer GetFeatureLayer( )
        {
            InMemoryFeatureLayer result = new InMemoryFeatureLayer( );


            GeoPen newGeoPen3 = new GeoPen( new GeoColor( 100, GeoColor.StandardColors.Black ) );
            newGeoPen3.Width = 0.3f;

            GeoPen newGeoPen1 = new GeoPen( new GeoColor( 100, GeoColor.StandardColors.Black ) );
            newGeoPen1.Width = 0.1f;


            result.Open( );
            result.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = TextStyles.State1( "name" );
            result.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.FillSolidBrush = new GeoSolidBrush( GeoColor.StandardColors.Black );
            result.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen = newGeoPen3;
            result.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            result.Columns.Add( new FeatureSourceColumn( "name", DbfColumnType.String.ToString( ), 100 ) );
            result.Columns.Add( new FeatureSourceColumn( "data", DbfColumnType.Double.ToString( ), 40 ) );

            result.EditTools.BeginTransaction( );

            var data = GetShapeFileBinaryData( Server.MapPath( "~/App_Data/48.imgData" ) );

            Feature feature = new Feature( data );

            feature.ColumnValues.Add( "name", "Texas" );

            feature.ColumnValues.Add( "data", "200" );

            feature.GetBoundingBox( ); //force the calculation.

            result.EditTools.Add( feature );

            result.EditTools.CommitTransaction( );

            result.BuildIndex( );

            return result;
        }


        private RectangleShape GetFullExtent( GeoCollection<Layer> Layers )
        {
            List<BaseShape> rectangleShapes = new List<BaseShape>( );

            foreach ( Layer layer in Layers )
            {
                layer.Open( );

                if ( layer.HasBoundingBox == true )
                    rectangleShapes.Add( layer.GetBoundingBox( ) );
            }

            return ExtentHelper.GetBoundingBoxOfItems( rectangleShapes );
        }

        public byte[] GetShapeFileBinaryData( string fileName )
        {


            byte[] data = null;

            using ( var stream = new FileStream( fileName, FileMode.Open, FileAccess.Read, FileShare.Read ) )
            {
                data = new byte[stream.Length];

                using ( BinaryReader reader = new BinaryReader( stream ) )
                {
                    reader.Read( data, 0, (int)stream.Length );
                }
            }

            return data;
        }


        [MapActionFilter]
        public void ClickEvent( Map map, GeoCollection<object> args )
        {
            var MyBitmap = map.GetBitmap( );
            MyBitmap.Save( @"d:\testmapImage2.png", ImageFormat.Png );
        }
    }
}



48.zip (588 KB)

Rick,


Changing two parts of your code will solve the problem,


Method ClickEvent



string[] Locations=((string)args[2]).Split(',');
map.CurrentExtent = new  RctangleShape(double.Parse(Locations[0]),double.Parse(Locations[3]),double.Parse(Locations[2]),double.Parse(Locations[1]));


Js part



var params = { x: e.worldXY.lon, y: e.worldXY.lat, extent: e.view.Map1.getExtent() };

Regards,


Edgar



 Thanks for the reply, that worked.


Now the question is, why and how would i find that in the documentation?


 


Rick



There is no documentation about this, the reason you get the wrong bitmap is the real map.CurrentExtent has not been updated when you click on it without a callback, so the width/height of extent is not equal to the width/height of the map, then the image would be stretched, so you need to get the real extent from the client side to get the right image. 
  
 Regards, 
 Edgar

I there any way to get the map extent without having to render it in the UI first?



Hi Chuck, 
  
 As far as I know, there isn’t a better/easier way to manage it, for the synchronization between client and server side is a bit complicated, which requires lots of codes to implement it, as the interaction is always a Ajax call and  if we want to keep the sync, a large number of data would be attached in every Ajax call which is not the purpose of Mvc. But, in the code behind mode, we can keep the sync easily by a post back. 
  
 Hope it helps. 
 Thanks 
  
 Johnny 


 Hi James,



Thanks for your post and welcome to MapSuite World, please get the latest version 6.0.0.338 or 6.0.338.0 first from helpdesk.thinkgeo.com , here we fixed 1 bug on Wms stuff, then I guess there are two ways to add WMS: 


1. Use a WMS plugin, attached is the sample of the plugin. Please build it and copy the dll to “C:\Program Files (x86)\ThinkGeo\Map Suite GIS Editor\Map Suite GISEditor\Plugins”


2. Use dynamic language to fulfill it, dynamic function is in here:




Then type in the popup window with the code in “Post11167SampleForDynamicLanguage.txt” attached.


The reason of not using wms as a default base layer is wms depends on the internet and sometimes it will cause our GisEditor stuck. We will fix this problem in the near future.


Hope it helps,


Johnny


 



Post11167SampleForDynamicLanguage.txt (941 Bytes)
Post11167Sample.zip (5.77 KB)

Hi Johnny, 
  
 Thanks for the reply.  I know that in web edition you can call GetBitMap on a map during initial page load (i.e., no post-back) and the image will be projected correctly.  If you try the same in an MVC controller (with Web or MVC edition) then the projection problem described by Rick will occurr. 
  
 How is it that the client-side extent is known in a webforms aspx page without posting back? 
  
 Thanks, 
  
 Chuck

 Hi Chuck,


If the clientmap.Currentextent and clientmap.HeightInPixels and clientmap.WidthInPixel are known in the serverside, then in the server side (controller in MVC) we can use:



servermap.Currentextent=map.Currentextent;

servermap.GetBitmap(map.WidthInPixel,map.HeightInPixel);

to get the right bitmap.


But, the problem is that the clientmap.Currentextent and clientmap.HeightInPixels and clientmap.WidthInPixel are calculated in the client-side and are not known in the serverside, server-side don’t know them because client-side doesn’t send them back to server-side yet, and therefore server-side can’t get the right bitmap in MVC.


So, to get these three parameters for server-side we need to use a postback or ajaxcall. As the question is “How is it that the client-side extent is known in a webforms aspx page without posting back?”, the choice would be to ajaxcall.


Attached is the sample of using ajaxcall to get the bitmap


Hope it helps


Gary



Post10859Sample.zip (1.32 KB)

Thanks Gary - that helps.



Glad to hear that it works. Thanks. 
  
 Johnny