Issue with ags/flex 3.1 api and "cloning" process

643
8
12-27-2012 01:55 PM
RoyceSimpson
Occasional Contributor III
Normally I can figure out where exceptions are being thrown but this one is isn't so easy.

I've got some pretty old code (see bottom of post) for creating a copy of a map used for pdf generation purposes.  For example, in order to create an 8.5x11 landscape pdf, I regenerate my current onscreen map in another offscreen container of the correct dimensions, then send that over to some AlivePDF routines to generate the ouput pdf.

This has all worked fine with all AGS/Flex API's up to 3.0.  With 3.1 I get the following error sometime after the map copy in the new window is generated but before the map actually draws.
TypeError: Error #1009: Cannot access a property or method of a null object reference.
 at com.esri.ags.layers::Layer/updateLayerIfInvalid()
 at com.esri.ags.layers::Layer/updateDisplayList()
 at mx.core::UIComponent/validateDisplayList()
 at mx.managers::LayoutManager/validateDisplayList()
 at mx.managers::LayoutManager/doPhasedInstantiation()
 at mx.managers::LayoutManager/doPhasedInstantiationCallback()


I know the above snippet is pretty generic but I'm not sure what else to post here.  The error seems to happen after the new container has the cloned map and right after I set the container.visible = true.  My guess is that there is something borked in the layers of the new cloned map but since I don't really have a good understanding of how the cloning stuff works, I can't go much further with that.

This has always been a kludge process to get PDF's out of these maps and I've never been happy with this process but it is what it is and it's baked into my apps.  Basically, the cloning looks like this... I'm sure most folks have seen this setup before.  If there is a better way (besides the new map template stuff, which won't work for my needs), please let me know.  The below code creates a copy of the main app map, puts it in a bordercontainer and returns the bordercontainer with map.  That bordercontainer is put in a titlewindow, off screen and then sent to the AlivePDF generation stuff.

    registerClassAlias("com.esri.ags.Graphic", Graphic);
    registerClassAlias("com.esri.ags.SpatialReference", SpatialReference);
    registerClassAlias("com.esri.ags.FeatureSet", FeatureSet);
    registerClassAlias("com.esri.ags.Map", Map);
    
    
    registerClassAlias("com.esri.ags.layers.ArcGISDynamicMapServiceLayer", ArcGISDynamicMapServiceLayer);
    registerClassAlias("com.esri.ags.layers.ArcGISTiledMapServiceLayer",ArcGISTiledMapServiceLayer);
    registerClassAlias("com.esri.ags.layers.GraphicsLayer", GraphicsLayer);
    registerClassAlias("com.esri.ags.layers.Layer", Layer);
    
    
    registerClassAlias("com.esri.ags.geometry.MapPoint", MapPoint);
    registerClassAlias("com.esri.ags.geometry.Multipoint", Multipoint);
    registerClassAlias("com.esri.ags.geometry.Polygon", Polygon);
    registerClassAlias("com.esri.ags.geometry.Polyline", Polyline);
    registerClassAlias("com.esri.ags.geometry.Extent", Extent);
    
    //Generic Symbols
    registerClassAlias("com.esri.ags.symbols.CompositeSymbol",CompositeSymbol);
    registerClassAlias("com.esri.ags.symbols.Symbol",Symbol);
    //Point Symbols
    registerClassAlias("com.esri.ags.symbols.SimpleMarkerSymbol",SimpleMarkerSymbol);
    registerClassAlias("com.esri.ags.symbols.PictureMarkerSymbol",PictureMarkerSymbol);
    registerClassAlias("com.esri.ags.symbols.TextSymbol",TextSymbol);
    registerClassAlias("com.esri.ags.symbols.InfoSymbol",InfoSymbol);
    //Line Symbols
    registerClassAlias("com.esri.ags.symbosl.LineSymbol", LineSymbol);
    registerClassAlias("com.esri.ags.symbols.SimpleLineSymbol", SimpleLineSymbol);
    registerClassAlias("com.esri.ags.symbols.CartographicLineSymbol", CartographicLineSymbol);
    //Polygon Symbols
    registerClassAlias("com.esri.ags.symbols.SimpleFillSymbol",SimpleFillSymbol);
    registerClassAlias("com.esri.ags.symbols.PictureFillSymbol", PictureFillSymbol);
    
    for each(var oLayer:Layer in map.layers)
    {
     if (oLayer is ArcGISDynamicMapServiceLayer)
     {
      var cLayer:ArcGISDynamicMapServiceLayer = deepClone(oLayer) as ArcGISDynamicMapServiceLayer;
      //cLayer.addEventListener(Event.COMPLETE,onLoadDone);
      printMap.addLayer(cLayer);
     } 
     
     else if (oLayer is ArcGISTiledMapServiceLayer) 
     {
      var tLayer:ArcGISTiledMapServiceLayer = deepClone(oLayer) as ArcGISTiledMapServiceLayer;
      /* var cLayer2:ArcGISDynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer(tLayer.url);
      cLayer2.id = tLayer.id;
      cLayer2.imageFormat = "jpg";
      cLayer2.addEventListener(Event.COMPLETE,onLoadDone);
      printMap.addLayer(cLayer2); */
      printMap.addLayer(tLayer);
     } 
      
     else if (oLayer is GraphicsLayer) 
     {
      var gLayer:GraphicsLayer = deepClone(oLayer) as GraphicsLayer;
      printMap.addLayer(gLayer);
     }
    }
    
    printMap.lods = null;
    printMap.scale = map.scale;
    printMap.scaleBarVisible = true;
    printMap.panArrowsVisible = false;
    printMap.zoomSliderVisible = false;
    printMap.logoVisible = false;
    printMap.useHandCursor = false;  
    
    var mp:MapPoint=new MapPoint(map.extent.center.x, map.extent.center.y)
    //printMap.centerAt(mp);
    printMap.extent=map.extent;
    
    return componentPrintBorderContainer;       


deepClone function
   private function deepClone ( obj : * ) : *                 
   {                        
    var bytes : ByteArray = new ByteArray();
    bytes.writeObject(obj);
    bytes.position = 0;
    return bytes.readObject();
   }
Tags (2)
0 Kudos
8 Replies
GISDev1
Occasional Contributor III
So why doesn't the new 10.1 printing stuff solve this problem for you?

Your custom solution is very cool though, just no longer necessary....
0 Kudos
RoyceSimpson
Occasional Contributor III
So why doesn't the new 10.1 printing stuff solve this problem for you?

Your custom solution is very cool though, just no longer necessary....


I haven't looked into the new template printing stuff too deeply but currently, the pdf outputs I create from the Flash client take data and maps already cooked up on the client side and create the pdf output. 
For example:
When a user has the overview map open in the client Flash app and click the "print" button, the the print will have an overview map in the corner with the extent box indicating the extent of the main map.  Conversely, if the user closes the overview map in the app, the print won't show it.  Also, if the user is running the measure tool and clicks the print button, the output print will have the measure results in the map surround area.  Also, users can have multiple map services displaying at one time, with varying degrees of transparency in the client map app.  Not sure how that would be accomplished via the server side map template stuff.

Any advice on whether/if the above stuff can be accomplished using the new server printing functionality would be greatly appreciated.  I'd love to ditch my "old" method for making prints but just don't alternate ways to do what I currently have.
0 Kudos
GISDev1
Occasional Contributor III
Yes, the new 10.1 print workflow will do all of those things and more, except possibly the measurement tool part (I haven't tested that).
0 Kudos
RoyceSimpson
Occasional Contributor III
Yes, the new 10.1 print workflow will do all of those things and more, except possibly the measurement tool part (I haven't tested that).


Thanks.  Am looking into it as we speak.

But in the meantime, if anyone has clues about regarding the origitanl post, please don't hesitate to chime in.  😉
0 Kudos
MarkHoyland
Occasional Contributor II
Hi Royce,

This is what I have been using for the last few years.

I built some functions to copy the map into a bitmap object and crop it to suit whatever size is needed.
It doesnt matter the size of the browser window it uses the ratio of the desired print size to crop the map.
The Bitmap can the be used in AlivePDF. (no cloning of maps needed).

As you say it is what it is and sometimes using the latest and greatest is not an option.
I have not tested in AGS Flex API 3.1 because our organisation is still on 10.x of flash, and 10.0 of ArcGIS.
Sounds like the new 10.1 print options would be great, but no good to me at the moment.

Basic rundown

When generating pdf use the PrintMapUtil.trimmedMap function.

protected function button1_clickHandler(event:MouseEvent):void
{
 var pdf:PDF  = new PDF(Orientation.LANDSCAPE,Unit.INCHES,Size.LETTER); 
 pdf.setDisplayMode (Display.FULL_PAGE);
 pdf.setMargins(0.5,0.5,0.5,0.5);
 
 pdf.addPage();    
 pdf.setAutoPageBreak(false,0.5); 
 
 //Use the PDF page margins for the width and height of printable map area.
 //The content width and height are the desired map size on the page. This example is
 //just fitting the map to the margins.
 var marginsRectangle:Rectangle = pdf.getMargins();
 var contentWidth:Number = marginsRectangle.width;
 var contentHeigth:Number = marginsRectangle.height ;
 
 var newBitmap:Bitmap = PrintMapUtil.trimmedMap(map, contentWidth, contentHeigth);
 // Add the map image
 pdf.addImage(newBitmap, null ,0, 0, 
  contentWidth , contentHeigth , 0, 1, true, ImageFormat.JPG, 75); 
 
 pdf.save(Method.REMOTE, 
  "http://alivepdf.bytearray.org/wp-content/demos/create.php", 
  Download.ATTACHMENT, "report.pdf"); 
}


public static function trimmedMap(map:Map, printAreaWidth:Number, printAreaHeight:Number):Bitmap
{
 var rectangle:Rectangle = clipRectangle(map, printAreaWidth, printAreaHeight);
 
 //get the map as bitmapdata so the part we want can be copied and clipped.
 var myBitmapData:BitmapData = new BitmapData(map.width,map.height);
 myBitmapData.draw(map,null,null,null,rectangle,true);
 
 // create a new cropped map image to fit the print area.
 var newBitmapData:BitmapData = new BitmapData(rectangle.width, rectangle.height)
 newBitmapData.copyPixels(myBitmapData, rectangle, new Point(0, 0));
 
 return new Bitmap(newBitmapData);
}



See attached for a sample project and the full PrintMapUtil with the clipRectangle function. (fxp file is inside the zip)
0 Kudos
RoyceSimpson
Occasional Contributor III
Mark, I'll try your approach, which isn't too different than mine... with the 3.1 API and see if it still breaks.  Still looking at the various other options presented as well.
0 Kudos
RoyceSimpson
Occasional Contributor III
Mark, I imported your sample project, changed the AGS/Flex API to 3.1, changed the path to my server side script, and it works fine.  Will see if I can change out some code and get mine to work.  Thanks.
0 Kudos
RoyceSimpson
Occasional Contributor III
After a bunch of troubleshooting and looking into other ways to do the kind of pdf generating that I'm doing... I'd sure like to stick with the method that I'm currently using (successfully with the 3.0 AGS/Flex API) as stated in the OP. 

I've added a quick and dirty example Flex project code that demonstrates the issue.  Paste it into your IDE, add the 3.1 SWC, run it.  Click the "clone" button.  The app throws exception.

UPDATE:
I was able to discover that if you only have one layer in the original map, the cloning process works fine.  So, if you comment out one of the tiled map services in the mxml portion of the provided example app, you'll see that the cloning works just fine.  I've set a couple alpha properties on the layers to show that they are in fact cloning properly, as the clone honors the alpha property of the original layer.
Notice also, that if you change out the 3.1 API for 3.0, the cloning process works fine if you have more than one map layer present.

Esri Flex API Dev staff, it would be great to get this capability added back to your next release of the API.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
      xmlns:s="library://ns.adobe.com/flex/spark"
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:esri="http://www.esri.com/2008/ags"
      minWidth="955" minHeight="600">
 
 <fx:Script>
  <![CDATA[
   import com.esri.ags.layers.ArcGISTiledMapServiceLayer;
   import com.esri.ags.layers.Layer;
   
   import flash.net.registerClassAlias;
   
   protected function button1_clickHandler(event:MouseEvent):void
   {
    registerClassAlias("com.esri.ags.layers.ArcGISTiledMapServiceLayer", ArcGISTiledMapServiceLayer);
    
    for each(var l:Layer  in map.layers)
    {
      if (l is ArcGISTiledMapServiceLayer)
      {
      var ctl:ArcGISTiledMapServiceLayer = deepClone(l) as ArcGISTiledMapServiceLayer;
      cloneMap.addLayer(ctl);
     }    
    } 
   }
   
   private function deepClone ( obj : * ) : *                 
   {                        
    var bytes : ByteArray = new ByteArray();
    bytes.writeObject(obj);
    bytes.position = 0;
    return bytes.readObject();
   }
  ]]>
 </fx:Script>
 
 <fx:Declarations>
  <esri:Extent id="initialExtent"
      xmin="-13635000" ymin="4541000" xmax="-13625000" ymax="4547000">
   <esri:SpatialReference wkid="102100"/>
  </esri:Extent>
 </fx:Declarations>
 
 <esri:Map id="map" x="12" y="57" width="376" height="230" extent="{initialExtent}">
  <esri:ArcGISTiledMapServiceLayer url="http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer" alpha=".5"/>
  <esri:ArcGISTiledMapServiceLayer url="http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer" alpha=".5"/>
 </esri:Map>
 <esri:Map id="cloneMap" x="506" y="52" width="399" height="269" extent="{initialExtent}"/>
 <s:Button x="210" y="347" label="Clone" click="button1_clickHandler(event)"/>
</s:Application>
0 Kudos