create new map instance (change projection)

1340
12
01-18-2011 03:10 AM
DmitryAleshkovsky
New Contributor III
I have problem with creating new map instance.
I have 2 tiled layers with different spatial reference (102100 and 54004)
i try to change my map baselayer from one layer to another one.
So I try to create new instance of the map with new extent and spatial reference. But layer no visible in new map (resolution = NaN)
my code:
lyr - is new layer

                
                Map.Layers.Clear();
                collLayers.Remove(collLayers["BaseLayer"]);//layers collection
                
                Map.UpdateLayout();
                lyr.ID = "BaseLayer";
                Map = new ESRI.ArcGIS.Client.Map() 
                { 
                    Extent = lyr.FullExtent, 
                    MaximumResolution=((TiledMapServiceLayer)lyr).TileInfo.Lods[0].Resolution ,
                    MinimumResolution = ((TiledMapServiceLayer)lyr).TileInfo.Lods[((TiledMapServiceLayer)lyr).TileInfo.Lods.Length-1].Resolution 
                };
                Map.Layers.Add(lyr);// add new base layer first
                foreach (Layer l in collLayers) // restore other layers
                {
                    Map.Layers.Add(l);
                }
                Map.UpdateLayout();


what's wrong?
0 Kudos
12 Replies
JenniferNery
Esri Regular Contributor
Kindly look at this related thread: http://forums.arcgis.com/threads/6553-Adding-Tiled-Map-services-of-different-spatial-reference (Specifically, Post #3).

It's hard to tell which part of your code fails.
Is collLayers the LayerCollection of first map? Or will the line above it (Map.Layers.Clear()) modify the same collection? If they are the same collection then, collLayers["BaseLayer"] will be null.
Has lyr been Initialized? If not, TileInfo may be null.

At any case, you cannot mix tiled layers of different spatial reference together as had been said in that forum.
0 Kudos
DmitryAleshkovsky
New Contributor III
Hi, Jennifer!

1/collLayers is the LayerCollection -copy of layers of the map
2/Map.Layers.Clear() don't modify this collection
3/collLayers["BaseLayer"] is the previous tiled layer in the map and lyr - is new tiled layerwith different projection.
4/ lyr has been Initialized, and I set spatial reference to  TileInfo too
5/ I know that I can't mix tiled layers, so i try recreate the map

6/ after this line map.resolution still null, I think this is a problem
                Map = new ESRI.ArcGIS.Client.Map() 
                { 
                    Extent = lyr.FullExtent, 
                    Visibility=Visibility.Visible,
                    MaximumResolution=((TiledMapServiceLayer)lyr).TileInfo.Lods[0].Resolution ,
                    MinimumResolution = ((TiledMapServiceLayer)lyr).TileInfo.Lods[((TiledMapServiceLayer)lyr).TileInfo.Lods.Length-1].Resolution 
                };


7/ I've tried add only ArcGISDynamicMapServiceLayer (SR -102100) in the new map, but have the same problem - no layers visible.

Do you know public tiled layer somewhere on internet with projection different 102100 to test application with it?
Or may be you have correct  code for creating new map instance.

Thank you.
0 Kudos
DmitryAleshkovsky
New Contributor III
2 tiled layers from ESRI
WGS84
http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer
Mercator 102100
http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer

XAML first layer added
[HTML] <esri:Map x:Name="Map" Background="White">

            <esri:ArcGISTiledMapServiceLayer ID="BaseLayer"
          Url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer"/>
            <!--<esri:ArcGISTiledMapServiceLayer ID="BaseLayer"
          Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer" />-->
            <esri:ArcGISDynamicMapServiceLayer ID="MyDynamicMapServiceLayer"
                    Url="http://MyServer/ArcGIS/rest/services/MyMap/MapServer"/>
</esri:Map>[/HTML]

Code Button click ... etc
private void mnuBaseMap_Click(object sender, RoutedEventArgs e)
        {
            Layer newLyr = null;
            int WKID = -1;
            collLayers = new LayerCollection();
            foreach (Layer  l in Map.Layers)
             {
                    collLayers.Add(l);
             }
            
            switch (((Button)sender).Name)
            {
                case "mnuESRIWorld":
                    newLyr = new ESRI.ArcGIS.Client.ArcGISTiledMapServiceLayer() 
                    { 
                        Url = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"
                     };
                    WKID = 102100;
                     break;
            }

            if (newLyr != null)
            {
                newLyr.Initialized += new EventHandler<EventArgs>(newLyr_Initialized);
                newLyr.Initialize();
            }

        }
  
void newLyr_Initialized(object sender, EventArgs e)
        {
            Layer newLayer = (Layer)sender;
            int WKID = 0;
            if (newLayer.SpatialReference != null)
                WKID = newLayer.SpatialReference.WKID;
            ChangeBaseLayer(newLayer, WKID);
            Map.UpdateLayout();
        }
  
 void ChangeBaseLayer(Layer lyr, int newWKID)
        {
            
            int baseIdx = Map.Layers.IndexOf(Map.Layers["BaseLayer"]);

            if (Map.SpatialReference.WKID == newWKID)
            {
 //skip 
            }
            else //recreate map
            {
                Map.Layers.Clear();
                collLayers.Remove(collLayers["BaseLayer"]);
                collLayers.Remove(collLayers["DummyMercator"]);
                Map.UpdateLayout();
                lyr.ID = "BaseLayer";
               ESRI.ArcGIS.Client.Map newMap = new ESRI.ArcGIS.Client.Map() 
                { 
                    Extent = lyr.FullExtent, 
                    Visibility=Visibility.Visible,
                    MaximumResolution=((TiledMapServiceLayer)lyr).TileInfo.Lods[0].Resolution ,
                    MinimumResolution = ((TiledMapServiceLayer)lyr).TileInfo.Lods[((TiledMapServiceLayer)lyr).TileInfo.Lods.Length-1].Resolution 
                };
               this.Map = newMap;
                Map.Layers.Add(lyr);
                foreach (Layer l in collLayers) //restore other layers
                {
                    Map.Layers.Add(l);
                }
                Map.UpdateLayout();
                
            }
        } 


Are Anybody can test it?
0 Kudos
JenniferNery
Esri Regular Contributor
I tried your sample and I faced the same issues so I think this would be a better approach.

I adapted the following code from this sample:
http://help.arcgis.com/en/webapi/silverlight/samples/start.htm#SwitchMap

XAML-code:
       xmlns:esri="http://schemas.esri.com/arcgis/client/2009">
 <Grid x:Name="LayoutRoot" Background="White">  
  <esri:Map x:Name="MyMap" Loaded="MyMap_Loaded">
   <esri:ArcGISTiledMapServiceLayer ID="BaseLayer" Visible="True" 
                    Url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer" />
   <esri:ArcGISDynamicMapServiceLayer ID="MyLayer"
                Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer" />
  </esri:Map>
  <Grid HorizontalAlignment="Right" VerticalAlignment="Top" Margin="10" >
   <Rectangle Fill="#77919191" Stroke="Gray"  RadiusX="10" RadiusY="10" Margin="0" >
    <Rectangle.Effect>
     <DropShadowEffect/>
    </Rectangle.Effect>
   </Rectangle>
   <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Background="Transparent" Margin="10">
    <RadioButton x:Name="StreetsRadioButton" 
                         Tag="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer"
                         IsChecked="true" Margin="5,0,0,0" Foreground="White"
                         GroupName="Layers" Content="4326" Click="RadioButton_Click"/>
    <RadioButton x:Name="TopoRadioButton" 
                         Tag="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer" 
                         Margin="5,0,0,0" Foreground="White" 
                         GroupName="Layers" Content="102100" Click="RadioButton_Click"/>    
   </StackPanel>
  </Grid>
 </Grid>


To switch out the base layer, you can first copy all other layers to another layer collection (just as you did), excluding the base layer that you want replaced.
Clear the old map layers so that you can add the copied layers to another map control.
Create a new map control.
Add the new base layer to this new map and add the other layers.
Once the new map control is set, you can replace the old map control with this.
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
 LayerCollection otherLayers = new LayerCollection();
 Map map = this.LayoutRoot.Children[0] as Map;
 foreach(var layer in map.Layers)
  if(!string.Equals(layer.ID, "BaseLayer", StringComparison.InvariantCultureIgnoreCase))
   otherLayers.Add(layer);
 map.Layers.Clear();
 ArcGISTiledMapServiceLayer arcgisLayer = new ArcGISTiledMapServiceLayer()
 {
  ID="BaseLayer",
  Url = ((RadioButton)sender).Tag as string
 };
 Map newMap = new Map();
 newMap.Layers.Add(arcgisLayer);
 foreach (var layer in otherLayers)
 newMap.Layers.Add(layer);
 this.LayoutRoot.Children[0] = newMap;
}

private void MyMap_Loaded(object sender, RoutedEventArgs e)
{
 MyMap.Focus();
}
0 Kudos
DmitryAleshkovsky
New Contributor III
Thank you Jennifer!
this code works nice!
I've found that after recreating the map with Mercator projection MyMap.Extention still -180+180 and -90+90. That was my error. I used the wrong variable.
Please explain me difference between this.MyMap and this.LayoutRoot.Children[0]. When I must use the first reference?
0 Kudos
JenniferNery
Esri Regular Contributor
The difference is - this.MyMap is an instance of a map control, this.LayoutRoot.Children[0] is the first UIElement in the grid. Since you will be switching two map controls in the same grid location, it would be easier to grab the element from the container rather than access the map instance by name.

Take the following for example. Let's say you need TextBox replaced with another TextBox.
<Grid x:Name="LayoutRoot">
<TextBox x:Name="MyTextBox"/>
</Grid>

You cannot replace MyTextBox with a new TextBox instance by accessing the old instance by name. You would only do so when you want to copy properties from another TextBox.
MyTextBox.Text = anotherTextBox.Text;

To replace the UIElement, you would do the following instead.
LayoutRoot.Children[0] = anotherTextBox;
0 Kudos
DmitryAleshkovsky
New Contributor III
Well,
so when i create new map I must give this instance the name of the previous map and set all parameters and attach events. All like in the previous map. Is it correct?

thank you very much!
0 Kudos
JenniferNery
Esri Regular Contributor
Good point, you need to unsubscribe the old map from its events and subscribe the new map to the same event handler.

I think you can only reuse the same name after you have removed the old map instance from your visual tree.
LayerCollection otherLayers = new LayerCollection();
Map map = this.LayoutRoot.Children[0] as Map;
map.Loaded -= MyMap_Loaded;
map.ExtentChanged -= MyMap_ExtentChanged;
foreach (var layer in map.Layers)
 if (!string.Equals(layer.ID, "BaseLayer", StringComparison.InvariantCultureIgnoreCase))
  otherLayers.Add(layer);
map.Layers.Clear();
ArcGISTiledMapServiceLayer arcgisLayer = new ArcGISTiledMapServiceLayer()
{
 ID = "BaseLayer",
 Url = ((RadioButton)sender).Tag as string
};
this.LayoutRoot.Children.Remove(map);
Map newMap = new Map() { Name = "MyMap" };
newMap.SetValue(Grid.RowProperty, 0);
this.LayoutRoot.Children.Insert(0, newMap);
newMap.Layers.Add(arcgisLayer);
newMap.Loaded += MyMap_Loaded;
newMap.ExtentChanged += MyMap_ExtentChanged;
foreach (var layer in otherLayers)
 newMap.Layers.Add(layer); 


However, in trying this code out. I found that this.MyMap still refers to the old instance. Same is true when I played with TextBox with TextChanged event.
private void MyMap_ExtentChanged(object sender, ExtentEventArgs e)
{
 ArcGISTiledMapServiceLayer baseLayer = this.MyMap.Layers[0] as ArcGISTiledMapServiceLayer;
}


this.MyMap.Layers.Count is 0 as this refers to the old instance.
(this.LayoutRoot.Children[0] as Map).Layers.Count is 2 as this refers to the new instance.
And if you check for this.LayoutRoot.Children[0] as Map).Name, it is MyMap.

I don't know if this is expected behavior or bug in VS as I found the same behavior with TextBox, where this.OldTb.Text still contained the old instance' text value after creating a new instance with the same name.

I think then that the best way to get your map is to know it's position in the visual tree
Map map = this.LayoutRoot.Children[0] as Map; //this is guaranteed to be the current map.
0 Kudos
DmitryAleshkovsky
New Contributor III
Hi, Jennifer!
I want to continue this thread (or create new one?)
In this example we know events of the current map object. But in the general case we don't know about all attached events of the object. So before creating new instance it will be good idea to copy event handlers from old map to somewhere (?) and restore them in new map. How make this?

Secondly, in XAML I have event triggers
[HTML]            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseClick">
                    <ei:ChangePropertyAction TargetName="myMenuItems" PropertyName="Visibility">
                        <ei:ChangePropertyAction.Value>
                            <Visibility>Collapsed</Visibility>
                        </ei:ChangePropertyAction.Value>
                    </ei:ChangePropertyAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
[/HTML]
new map also must have triggers. How to restore this part of code?

3/ also the "problem" with Map Properties

Eventually we have a problem with cloning of map object (or collections of events, triggers,properties, what else?)

in addition. If the reference to the old map is still exists, maybe necessary remove events handlers from it? to prevent raise of events twice. Or kill old map?
0 Kudos