在SuperMap IS .NET企业版中使用VML进阶
                    
 作者:bnucougar

   随着SuperMap IS .NET企业版的推出,最新开发的智能集群缓存技术(IC&C)、缓存持久化服务,数百倍地提高了并发支持能力。而VML技术与缓存技术的结合,不但可以得到更加出色的客户端效果,而且还可以使得缓存技术效果发挥得淋漓尽致。因此继《在SuperMap IS .NET中使用VML》之后,笔者又将VML的使用进行到底。

  本文将以“在选择某一个区域中的对象后,保存这个区域,并且这个区域随地理位置的移动而移动,随地图比例尺的缩放而缩放”为例讲解如何在SuperMap IS .NET企业版中使用VML的高级特性。


1.1 实现思路

1.1.1 第一种实现思路

  参考《在SuperMap IS .NET中使用VML》中的方法来实现,实现思路是比较简单的。按照上文提到的方法,在后台得到地图缩放的比例,因为这个比例只能通过后台代码得到,在前台算出新的VML中的Rect对象的大小,之后划上去。移动的情况类似,通过计算往哪个方向移动,移动了多少,然后在新的位置添加。

  但是,笔者在实现的时候出现了问题,缩放的效果正确,但是移动的时候,只有往右下方向移动才可以,其他方向总是不对,后来考虑可能是VML中的坐标原点和SuperMap中的不一致的原因吧,有感兴趣的朋友可以联系笔者,看看这种方式应该如何解决。

 
 

 



1.1.2 第二种实现思路


  正因为第一种思路实现不了,笔者重新考虑了解决问题的思路,认为使用MapControl的CustomLayer更加方便。下面我们看一下具体实现的代码。

1.2 具体实现

1.2.1 添加自定义鼠标事件

  这里笔者使用Microsoft Visual Studio 2005(下文简称VS2005)来实现。首先在VS2005环境里面添加一个WebForm项,名字就用默认的Default.aspx,语言选择为C#。在这个页面的HTML代码中修改以下form的load事件,代码如下:


  <form id="form1" runat="server" oninit="Default_Init">

  在Default.aspx.cs添加代码如下


  protected void Default_Init(object sender, EventArgs e)
  {
   this.AddAction();
  }

  在Default.aspx.cs添加AddAction()代码如下:


  public void AddAction()
  {
   //添加一个自定义的鼠标事件
   //其中第一个参数"DrawVMLByRect"是
   //在HTML代码中JavaScript调用SMISActionStart函数的第二个参数的名称
   //第二个参数是鼠标事件的预先定义的Action 类型,如平移、拉框、画多边形等
   Action SelectByRectAction = new Action("DrawVMLByRect", Action.DefinedModel.DrawRect);
   //定义自定义鼠标事件的鼠标
   SelectByRectAction.MapCursorUrl = "images/cur_RECTQUERY.cur";
   //添加自定义鼠标事件的委托
   SelectByRectAction.ActionEvent += new          
   SuperMap.IS.WebControls.Utility.Action.ActionEventHandler(DrawVMLByRectAction_ActionEvent);
   //添加这个自定义鼠标事件到MapControl的自定义地图操作Actions里面
   this.MapControl1.Actions.Insert(SelectByRectAction);
  }

1.2.2 添加自定义鼠标事件的实现代码

  在Default.aspx.cs添加DrawVMLByRectAction_ActionEvent事件代码如下:


   /// <summary>
   /// 划VML Rect对象的方法
   /// </summary>
   /// <param name="sender"></param>
   /// <param name="e">可以通过e.MapCoords属性得到鼠标点击操作的坐标</param>
   private void DrawVMLByRectAction_ActionEvent(object sender, ActionEventArgs e)
   {
    //将Rect对象的坐标通过鼠标事件得到
    //并且存放到Session对象里面
    Session["Start_X"] = e.MapCoords[0].X;
    Session["Start_Y"] = e.MapCoords[0].Y;
    Session["End_X"] = e.MapCoords[1].X;
    Session["End_Y"] = e.MapCoords[1].Y;

    //用两对double类型的变量得到存放在Session里面的坐标信息
    double Start_X = Convert.ToDouble(Session["Start_X"]);
    double Start_Y = Convert.ToDouble(Session["Start_Y"]);
    double End_X = Convert.ToDouble(Session["End_X"]);
    double End_Y = Convert.ToDouble(Session["End_Y"]);

    //得到当前MapControl 控件的宽度和高度
    string widthpx = this.MapControl1.Width.ToString();
    string heightpx = this.MapControl1.Height.ToString();
    //因为返回值为System.Web.UI.WebControls.Unit类型的
    //需要得到具体的数值,然后进行计算
    string strWidth = widthpx.Substring(0, widthpx.Length - 2);
    string strHeight = heightpx.Substring(0, heightpx.Length - 2);

    //通过计算算出VML宽度和高度
    double dVmlWidth = (End_X - Start_X) / (this.MapControl1.ViewBounds.LeftBottom.X -         this.MapControl1.ViewBounds.RightTop.X) * Convert.ToDouble(strWidth);
    dVmlWidth = Math.Abs(dVmlWidth);
    double dVmlHeight = (End_Y - Start_Y) / (this.MapControl1.ViewBounds.RightTop.Y -     this.MapControl1.ViewBounds.LeftBottom.Y) * Convert.ToDouble(strHeight);
    dVmlHeight = Math.Abs(dVmlHeight);

    //创建添加VML操作的innerHTML
    string strVML = "";
    strVML += string.Format("<v:rect style='position:relative;width:{0}px;height:{1}px'>", dVmlWidth,     dVmlHeight);
    strVML += "<v:fill opacity=0.3 color='#F95858'></v:fill>";
    strVML += "<v:stroke dashstyle='solid' Color='#316AC5' weight='1px'></v:stroke>";
    strVML += "</v:rect>";
    //算出添加VML Rect对象的中心点
    double Point_CenterX = Start_X + (End_X - Start_X) / 2;
    double Point_CenterY = End_Y - (End_Y - Start_Y) / 2;

    //在MapControl的CustomLayer上面添加HTML代码
    this.MapControl1.CustomLayer.Divs.Insert("vml", Point_CenterX, Point_CenterY, strVML);
    //设置MapControl的CustomLayer可见
    this.MapControl1.CustomLayer.Visible = true;

    //for bug
    System.Diagnostics.Debug.WriteLine("dVmlWidth=" + dVmlWidth);
    System.Diagnostics.Debug.WriteLine("dVmlHeight=" + dVmlHeight);
    }

1.2.3 添加MapControl范围移动情况下的事件处理

  在Default.aspx的MapContrl控件中添加代码:


  OnViewBoundsChanged="MapControl1_ViewBoundsChanged"

  在Default.aspx.cs添加代码如下:


   protected void MapControl1_ViewBoundsChanged(object sender, ViewBoundsChangedEventArgs e)
   {
    /************************************************************************/
    /* move the vml rect with the bounds change
    * */
    /************************************************************************/
    if (e.ViewBoundsBefore != null)
    {
     //get the width change rate named rate_x
     double CurrentWidth = e.ViewBoundsAfter.RightTop.X - e.ViewBoundsAfter.LeftBottom.X;
     double OldWidth = e.ViewBoundsBefore.RightTop.X - e.ViewBoundsBefore.LeftBottom.X;
     double rate_X = CurrentWidth / OldWidth;
     //get the height change rate named rate_y
     double CurrentHeight = e.ViewBoundsAfter.RightTop.Y - e.ViewBoundsAfter.LeftBottom.Y;
     double OldHeight = e.ViewBoundsBefore.RightTop.Y - e.ViewBoundsBefore.LeftBottom.Y;
     double rate_Y = CurrentHeight / OldHeight;

     //用两对double类型的变量得到存放在Session里面的坐标信息
     double Start_X = Convert.ToDouble(Session["Start_X"]);
     double Start_Y = Convert.ToDouble(Session["Start_Y"]);
     double End_X = Convert.ToDouble(Session["End_X"]);
     double End_Y = Convert.ToDouble(Session["End_Y"]);

     //得到当前MapControl 控件的宽度和高度
     string widthpx = this.MapControl1.Width.ToString();
     string heightpx = this.MapControl1.Height.ToString();
     //因为返回值为System.Web.UI.WebControls.Unit类型的
     //需要得到具体的数值,然后进行计算
     string strWidth = widthpx.Substring(0, widthpx.Length - 2);
     string strHeight = heightpx.Substring(0, heightpx.Length - 2);

     //计算出新的Rect对象的宽度
     double dVmlWidth = (End_X - Start_X) / (this.MapControl1.ViewBounds.LeftBottom.X - this.MapControl1.ViewBounds.RightTop.X) * Convert.ToDouble(strWidth);
     rate_X = Math.Round(rate_X, 5);
     if (rate_X > 1)
     {
      dVmlWidth = Math.Abs(dVmlWidth) / rate_X * 2;
     }
     else if (rate_X < 1)
     {
      dVmlWidth = Math.Abs(dVmlWidth) / rate_X / 2;
     }
     else if (rate_X == 1)
     {
      dVmlWidth = Math.Abs(dVmlWidth);
     }

     //计算出新的Rect对象的宽度
     double dVmlHeight = (End_Y - Start_Y) / (this.MapControl1.ViewBounds.RightTop.Y -      this.MapControl1.ViewBounds.LeftBottom.Y) * Convert.ToDouble(strHeight);
     rate_Y = Math.Round(rate_Y, 5);
     if (rate_Y > 1)
     {
      dVmlHeight = Math.Abs(dVmlHeight) / rate_Y * 2;
     }
     else if (rate_Y < 1)
     {
      dVmlHeight = Math.Abs(dVmlHeight) / rate_Y / 2;
     }
     else if (rate_Y == 1)
     {
      dVmlHeight = Math.Abs(dVmlHeight);
}

     //for debug
     System.Diagnostics.Debug.WriteLine("dVmlWidth=" + dVmlWidth);
     System.Diagnostics.Debug.WriteLine("dVmlHeight=" + dVmlHeight);

     //创建添加VML操作的innerHTML
     string strVML = "";
     strVML += string.Format("<v:rect style='position:relative;width:{0}px;height:{1}px'>", dVmlWidth, dVmlHeight);
     strVML += "<v:fill opacity=0.3 color='#F95858'></v:fill>";
     strVML += "<v:stroke dashstyle='solid' Color='#316AC5' weight='1px'></v:stroke>";
     strVML += "</v:rect>";

     //在MapControl的CustomLayer上面添加HTML代码
     this.MapControl1.CustomLayer.Divs.Insert("vml", Start_X + (End_X - Start_X) / 2, End_Y - (End_Y - Start_Y) / 2, strVML);
     //设置MapControl的CustomLayer可见
     this.MapControl1.CustomLayer.Visible = true;
    }
   }

1.3 实现结果

  下图显示了利用VML画出的拉框范围:

         

                         图1 保存拉框范围

1.4 总结


1.4.1 还需要解决的问题

1.4.1.1 VML对象部分溢出

  问题是划出来的VML的Rect对象移动到地图图片外面的情况,如下面的截图所示:

         

                    图2 Rect对象范围超出Map范围的情况

  这个问题的解决可以在计算新的Rect对象的范围的时候,添加判断,使得Rect对象不会到地图的外面。

1.4.1.2 VML对象覆盖整个区域

         
                      图3 VML的Rect对象过大的情况

  如果前面的问题可以通过点击按钮来清除这个Rect对象的话,对于现在这种情况就无能为力了。所以,这种情况可以通过在VML对象上面点击右键清除的方式来实现,感兴趣的朋友可以自己尝试一下。当然,如果第一种问题解决了的话,第二种情况也不会出现。

1.4.2 相关说明

  笔者在解决问题的时候使用了Session对象,关于Session对象的释放问题,有一个小小的说明,可以在body的onunload事件中添加一个页面的转向,在那个页面里面把需要释放的Session变量清空,不会因为Session引起不可预料的问题。