ArcGIS API for Silverlight开发入门(4):用户与地理信息之间的桥梁--GraphicsLayer

本文主要是介绍ArcGIS API for Silverlight开发入门(4):用户与地理信息之间的桥梁--GraphicsLayer,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们与地图交互的过程时刻在进行着:一个拉框放大操作,或者对地图内容的查询等。这些交互过程中的输入输出,通常都是反映在独立于地图数据一个“层”上。比如拉框放大,我们能看见鼠标所画的一个矩形;又比如对兴趣点的查询,结果通常是将符合条件的兴趣点的形状高亮显示在那个独立的“层”中,通过它既可以反映用户的输入,又可以展现地图的输出。这个“层”就是GraphicsLayer。
        其实ADF开发中也有GraphicsLayer的概念,同样在其他两个客户端API(JavaScript/Flex)中也能找到GraphicsLayer的身影,它们都是一样一样的。
        本节我们主要看如何在GraphicsLayer中展现内容。当然第一个工作就是添加ESRI.ArcGIS.dll的引用,引入esri的xml命名空间;接下来在Map中添加一个GraphicsLayer图层:
  1. <esri:Map x:Name="Map1">
  2. <esri:Map.Layers>
  3. <!-- 其他图层 -->
  4. <esri:GraphicsLayer ID="GLayer" />
  5. </esri:Map.Layers>
  6. </esri:Map>
复制代码
要使GraphicsLayer中的内容处于最顶端(不被其他图层内容覆盖),就要将它放在Map标签里的最下头,像上面那样。从命名我们不难看出,GraphicLayer里面放的就是Graphic的集合了。Graphic(ESRI.ArcGIS.Graphic)是GraphicsLayer中的基本元素,它包括了Geometry(在ESRI.ArcGIS.Geometry命名空间中),Symbol(在ESRI.ArcGIS.Symbol命名空间中),Attributes等属性。所有显示在地图中的矢量元素都有一个Geometry,里面包含了若干地理坐标,用于显示地图上地物的形状,它是Point,Polyline,Polygon等的总称,在这里代表了Graphic的形状。Symbol代表了Graphic的外观,它是一系列符号的总称,我们通常跟SimpleMarkerSymbol,SimpleLineSymbol和SimpleFillSymbol等打交道,它们分别对应了上面3种不同的Geometry(Point,Polyline,Polygon)。
        要让一个Graphic显示出来,总共分3步:
1、定义Graphic:
在xaml中
  1. <esri:Graphic>
  2. </esri:Graphic>
复制代码
在code-behind中
Graphic g= new Graphic()
2、设置Graphic的Geometry和Symbol属性:
在xaml中
  1. <esri:Graphic>
  2. <esri:Graphic.Symbol>
  3. <esriSymbols:SimpleMarkerSymbol Color="Blue" Size="12" Style="Square" />
  4. </esri:Graphic.Symbol>
  5. <esriGeometry:MapPoint X="108" Y="30" />
  6. </esri:Graphic>
复制代码
在code-behind中
  1. Graphic g = new Graphic()
  2. {
  3. Geometry = new MapPoint(108, 30),
  4. Symbol = new SimpleMarkerSymbol()
  5. {
  6. Color = new SolidColorBrush(Colors.Blue),
  7. Size = 12,
  8. Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
  9. }
  10. };
复制代码
3、把定义好的Graphic添加到GraphicsLayer里:
在xaml中
  1. <esri:GraphicsLayer ID="GLayer">
  2. <esri:GraphicsLayer.Graphics>
  3. <esri:Graphic>
  4. <esri:Graphic.Symbol>
  5. <esriSymbols:SimpleMarkerSymbol Color="Blue" Size="12" Style="Square" />
  6. </esri:Graphic.Symbol>
  7. <esriGeometry:MapPoint X="108" Y="30" />
  8. </esri:Graphic>
  9. </esri:GraphicsLayer.Graphics>
  10. </esri:GraphicsLayer>
复制代码
在code-behind中
  1. Graphic g = new Graphic()
  2. {
  3. Geometry = new MapPoint(108, 30),
  4. Symbol = new SimpleMarkerSymbol()
  5. {
  6. Color = new SolidColorBrush(Colors.Blue),
  7. Size = 12,
  8. Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
  9. }
  10. };
  11. GraphicsLayer glayer = Map1.Layers["GLayer"] as GraphicsLayer;
  12. glayer.Graphics.Add(g);
复制代码
看一下效果:

04_graphic.png

下载 (246.21 KB)
2009-4-20 18:27

 


        图中还有其他的图形,无非是改变了Graphic的Geometry和Symbol属性。图上的那只灰熊是一段动画文件,利用Silverlight的特性,能够定义出表现力丰富的各种符号。
        尽管能够完全在xaml中来完成工作,但还是建议将可视化元素的定义放在xaml中,将实现的逻辑部分放在code-behind中。看一下添加图中那些Graphic的代码:
  1. <Grid.Resources>
  2. <esriSymbols:SimpleMarkerSymbol x:Name="RedMarkerSymbol" Color="Red" Size="12" Style="Circle" />
  3. <!-- 可惜目前Silverlight只支持Jpeg和PNG格式的图像,所以PictureMarkerSymbol无法显示GIF格式的图像,否则会报ImagingError的错误 -->
  4. <esriSymbolsictureMarkerSymbol x:Name="PinPictureMarkerSymbol" Source="imgs/pin.png" OffsetX="10" OffsetY="10" />
  5. <esriSymbols:SimpleLineSymbol x:Name="RedLineSymbol" Color="Red" Width="4" Style="Solid" />
  6. <esriSymbols:CartographicLineSymbol x:Name="CartoLineSymbol" Color="Red" Width="10" DashCap="Triangle" LineJoin="Round" DashArray="6,2" />
  7. <esriSymbols:SimpleFillSymbol x:Name="RedFillSymbol" Fill="#66FF0000" BorderBrush="Red" BorderThickness="2" />
  8. </Grid.Resources>

  9. <MediaElement x:Name="BearVideo" />
复制代码
  1. private void AddGraphics()
  2. {
  3. GraphicsLayer glayer = Map1.Layers["GLayer"] as GraphicsLayer;
  4. Graphic[] graphics = new Graphic[8];
  5. graphics[0] = new Graphic()
  6. {
  7. Geometry = new MapPoint(108, 34),
  8. Symbol = RedMarkerSymbol
  9. };

  10. graphics[1] = new Graphic()
  11. {
  12. Geometry = new MapPoint(108, 30),
  13. Symbol = new SimpleMarkerSymbol()
  14. {
  15. Color = new SolidColorBrush(Colors.Blue),
  16. Size = 12,
  17. Style = SimpleMarkerSymbol.SimpleMarkerStyle.Square
  18. }
  19. };

  20. graphics[2] = new Graphic()
  21. {
  22. Geometry = new MapPoint(108, 25),
  23. Symbol = PinPictureMarkerSymbol
  24. };

  25. graphics[3] = new Graphic()
  26. {
  27. Geometry = new MapPoint(108, 20),
  28. Symbol = new TextSymbol()
  29. {
  30. FontFamily = new FontFamily("微软雅黑, 宋体"),
  31. FontSize = 14,
  32. Foreground = new SolidColorBrush(Colors.Black),
  33. Text = "这是text symbol"
  34. }
  35. };

  36. graphics[4] = new Graphic();
  37. graphics[4].Symbol = RedLineSymbol;
  38. ESRI.ArcGIS.Geometry.PointCollection pc = new ESRI.ArcGIS.Geometry.PointCollection()
  39. {
  40. new MapPoint(95,10),
  41. new MapPoint(110,-15),
  42. new MapPoint(130,10)
  43. };
  44. ESRI.ArcGIS.Geometry.Polyline pl = new ESRI.ArcGIS.Geometry.Polyline();
  45. pl.Paths.Add(pc);
  46. graphics[4].Geometry = pl;

  47. graphics[5] = new Graphic();
  48. graphics[5].Symbol = CartoLineSymbol;
  49. ESRI.ArcGIS.Geometry.PointCollection pc1 = new ESRI.ArcGIS.Geometry.PointCollection()
  50. {
  51. new MapPoint(95,0),
  52. new MapPoint(110,-25),
  53. new MapPoint(130,0)
  54. };
  55. ESRI.ArcGIS.Geometry.Polyline pl1 = new ESRI.ArcGIS.Geometry.Polyline();
  56. pl1.Paths.Add(pc1);
  57. graphics[5].Geometry = pl1;

  58. graphics[6] = new Graphic()
  59. {
  60. Symbol = RedFillSymbol
  61. };
  62. ESRI.ArcGIS.Geometry.PointCollection pc2 = new ESRI.ArcGIS.Geometry.PointCollection()
  63. {
  64. new MapPoint(110,-30),
  65. new MapPoint(130,-30),
  66. new MapPoint(130,-45),
  67. new MapPoint(120,-55),
  68. new MapPoint(110,-45),
  69. new MapPoint(110,-30)
  70. };
  71. ESRI.ArcGIS.Geometry.Polygon pg = new ESRI.ArcGIS.Geometry.Polygon();
  72. pg.Rings.Add(pc2);
  73. graphics[6].Geometry=pg;

  74. graphics[7] = new Graphic();
  75. //MediaElement的Name属性只能在xaml中定义(见帮助),所以决定了MediaElement不能完全在cs代码中定义
  76. BearVideo.Source = new Uri("http://serverapps.esri.com/media/bear.wmv", UriKind.RelativeOrAbsolute);
  77. BearVideo.IsHitTestVisible=false;
  78. BearVideo.IsMuted=true;
  79. BearVideo.AutoPlay=true;
  80. BearVideo.Opacity=0;
  81. ESRI.ArcGIS.Geometry.Polygon pg2 = new ESRI.ArcGIS.Geometry.Polygon();
  82. ESRI.ArcGIS.Geometry.PointCollection pc3 = new ESRI.ArcGIS.Geometry.PointCollection()
  83. {
  84. new MapPoint(10,-20),
  85. new MapPoint(32,7),
  86. new MapPoint(62,-35),
  87. new MapPoint(11,-36),
  88. new MapPoint(10,-20)
  89. };
  90. pg2.Rings.Add(pc3);
  91. graphics[7].Geometry=pg2;
  92. graphics[7].Symbol = new SimpleFillSymbol()
  93. {
  94. Fill = new VideoBrush()
  95. {
  96. SourceName = BearVideo.Name,
  97. Opacity = 0.6,
  98. Stretch = Stretch.UniformToFill
  99. }
  100. };


  101. foreach (Graphic g in graphics)
  102. {
  103. glayer.Graphics.Add(g);
  104. g.MouseLeftButtonDown+=new MouseButtonEventHandler(graphic_MouseLeftButtonDown);
  105. }
  106. }

  107. private void graphic_MouseLeftButtonDown(object o,MouseButtonEventArgs e)
  108. {
  109. Graphic g=o as Graphic;
  110. MessageBox.Show(string.Format("Geometry:{0}\nSymbol:{1}",g.Geometry.GetType().ToString(),g.Symbol.GetType().ToString()));
  111. }
复制代码
可以看到,完全能够在一个Graphic上定义一些事件,来达到程序的目的。大家可以试着把上面的内容在xaml中改写一遍。看到这里肯定会产生一个疑问:难道每个Geometry的定义都这么困难吗?其实SilverlightAPI已经给我们提供了ESRI.ArcGIS.Draw(继承自xaml中的Canvas)类,它能非常方便的捕捉到用户的鼠标操作,从而获取各种Geometry来供程序使用。
        可以把Draw理解成一块画板,调用Draw的Active()方法,就可以开始在画板上面绘画,程序会自动记录鼠标画出的每个Geometry,调用DeActive()方法,停止绘画。Active()有一个DrawMode参数,它决定了我们即将在这个画板上画出的内容类型:Point,Polyline,Polygon等。在画的过程中我们可以看到地图上可以实时反映出我们绘画的内容,而这些则利用了Draw的预定义Symbol:DefaultMarkerSymbol,DefaultLineSymbol,DefaultPolygonSymbol等。对应关系如下:

04_drawmode.png

下载 (5.8 KB)
2009-4-20 18:27

 


        每当完成一个图形的绘制,就会触发Draw.OnDrawComplete事件,利用事件参数就可以获得Geometry,之后可以创建一个Graphic,设置一个Symbol(一般使用Draw的预定义Symbol),把画好的这个Graphic添加到一个GraphicsLayer中。
         点击这里,查看一个比较完整的Graphics的例子。
最后来看一下这个例子的部分代码:
  1. <Grid.Resources>
  2. <esriSymbols:SimpleMarkerSymbol x:Name="DefaultMarkerSymbol" Color="Red" Size="12" Style="Circle" />
  3. <esriSymbols:CartographicLineSymbol x:Name="DefaultLineSymbol" Color="Red" Width="4" />
  4. <esriSymbols:SimpleFillSymbol x:Name="DefaultFillSymbol" Fill="#33FF0000" BorderBrush="Red" BorderThickness="2" />
  5. <esriSymbols:SimpleFillSymbol x:Name="DefaultPolygonSymbol" Fill="#33FF0000" BorderBrush="Red" BorderThickness="2" />
  6. </Grid.Resources>

  7. <esriraw x:Name="Draw1"
  8. DefaultRectangleSymbol="{StaticResource DefaultFillSymbol}"
  9. DefaultMarkerSymbol="{StaticResource DefaultMarkerSymbol}"
  10. DefaultLineSymbol="{StaticResource DefaultLineSymbol}"
  11. DefaultPolygonSymbol="{StaticResource DefaultPolygonSymbol}"
  12. Loaded="Draw1_Loaded"
  13. OnDrawComplete="Draw1_OnDrawComplete" />

  14. <Canvas VerticalAlignment="Top" HorizontalAlignment="Left" Margin="20,20,0,0" Width="430" Height="110">
  15. <Rectangle RadiusX="10" RadiusY="10" Width="430" Height="110" Fill="#98000000" Stroke="#FF6495ED" />
  16. <Rectangle Fill="#FFFFFFFF" Stroke="DarkGray" RadiusX="5" RadiusY="5" Canvas.Left="10" Canvas.Top="10" Width="410" Height="90" />

  17. <StackPanel Orientation="Vertical" Canvas.Top="5" Canvas.Left="20">
  18. <esriWidgets:Toolbar x:Name="ToolBar1" MaxItemHeight="80" MaxItemWidth="80" Width="380" Height="80"
  19. ToolbarIndexChanged="ToolBar1_ToolbarIndexChanged"
  20. ToolbarItemClicked="ToolBar1_ToolbarItemClicked">
  21. <esriWidgets:Toolbar.Items>
  22. <esriWidgets:ToolbarItemCollection>
  23. <esriWidgets:ToolbarItem Text="添加点">
  24. <esriWidgets:ToolbarItem.Content>
  25. <Image Source="imgs/DrawPoint.png" Stretch="UniformToFill" Margin="5" />
  26. </esriWidgets:ToolbarItem.Content>
  27. </esriWidgets:ToolbarItem>
  28. <esriWidgets:ToolbarItem Text="添加折线">
  29. <esriWidgets:ToolbarItem.Content>
  30. <Image Source="imgs/DrawPolyline.png" Stretch="UniformToFill" Margin="5" />
  31. </esriWidgets:ToolbarItem.Content>
  32. </esriWidgets:ToolbarItem>
  33. <esriWidgets:ToolbarItem Text="添加多边形">
  34. <esriWidgets:ToolbarItem.Content>
  35. <Image Source="imgs/DrawPolygon.png" Stretch="UniformToFill" Margin="5" />
  36. </esriWidgets:ToolbarItem.Content>
  37. </esriWidgets:ToolbarItem>
  38. <esriWidgets:ToolbarItem Text="添加矩形">
  39. <esriWidgets:ToolbarItem.Content>
  40. <Image Source="imgs/DrawRectangle.png" Stretch="UniformToFill" Margin="5" />
  41. </esriWidgets:ToolbarItem.Content>
  42. </esriWidgets:ToolbarItem>
  43. <esriWidgets:ToolbarItem Text="添加曲线">
  44. <esriWidgets:ToolbarItem.Content>
  45. <Image Source="imgs/DrawFreehand.png" Stretch="UniformToFill" Margin="5" />
  46. </esriWidgets:ToolbarItem.Content>
  47. </esriWidgets:ToolbarItem>
  48. <esriWidgets:ToolbarItem Text="停止添加动作">
  49. <esriWidgets:ToolbarItem.Content>
  50. <Image Source="imgs/StopDraw.png" Stretch="UniformToFill" Margin="5" />
  51. </esriWidgets:ToolbarItem.Content>
  52. </esriWidgets:ToolbarItem>
  53. <esriWidgets:ToolbarItem Text="清空绘制的图形">
  54. <esriWidgets:ToolbarItem.Content>
  55. <Image Source="imgs/eraser.png" Stretch="UniformToFill" Margin="5" />
  56. </esriWidgets:ToolbarItem.Content>
  57. </esriWidgets:ToolbarItem>
  58. </esriWidgets:ToolbarItemCollection>
  59. </esriWidgets:Toolbar.Items>
  60. </esriWidgets:Toolbar>
  61. <TextBlock x:Name="StatusTextBlock" Text="" FontWeight="Bold" HorizontalAlignment="Center"/>
  62. </StackPanel>
  63. </Canvas>
复制代码
  1. private void Draw1_Loaded(object sender, RoutedEventArgs e)
  2. {
  3. Draw1.Map = Map1;
  4. }

  5. private void Draw1_OnDrawComplete(object sender, ESRI.ArcGIS.DrawEventArgs args)
  6. {
  7. ESRI.ArcGIS.GraphicsLayer graphicsLayer = Map1.Layers["GLayer2"] as ESRI.ArcGIS.GraphicsLayer;
  8. ESRI.ArcGIS.Graphic graphic = new ESRI.ArcGIS.Graphic()
  9. {
  10. Geometry = args.Geometry,
  11. Symbol = _activeSymbol,
  12. };
  13. graphicsLayer.Graphics.Add(graphic);
  14. }

  15. private void ToolBar1_ToolbarIndexChanged(object sender, ESRI.ArcGIS.Widgets.SelectedToolbarItemArgs e)
  16. {
  17. StatusTextBlock.Text = e.Item.Text;
  18. }

  19. private void ToolBar1_ToolbarItemClicked(object sender, ESRI.ArcGIS.Widgets.SelectedToolbarItemArgs e)
  20. {
  21. Draw1.Deactivate();
  22. switch (e.Index)
  23. {
  24. case 0: // Point
  25. Draw1.Activate(ESRI.ArcGIS.DrawMode.Point);
  26. _activeSymbol = strobeSymbol;
  27. break;
  28. case 1: // Polyline
  29. Draw1.Activate(ESRI.ArcGIS.DrawMode.Polyline);
  30. _activeSymbol = DefaultLineSymbol;
  31. break;
  32. case 2: // Polygon
  33. Draw1.Activate(ESRI.ArcGIS.DrawMode.Polygon);
  34. _activeSymbol = DefaultPolygonSymbol;
  35. break;
  36. case 3: // Rectangle
  37. Draw1.Activate(ESRI.ArcGIS.DrawMode.Rectangle);
  38. _activeSymbol = DefaultFillSymbol;
  39. break;
  40. case 4: // Freehand
  41. Draw1.Activate(ESRI.ArcGIS.DrawMode.Freehand);
  42. _activeSymbol = waveLineSymbol;
  43. break;
  44. case 5: // Stop Graphics
  45. break;
  46. case 6: // Clear Graphics
  47. ESRI.ArcGIS.GraphicsLayer graphicsLayer = Map1.Layers["GLayer2"] as ESRI.ArcGIS.GraphicsLayer;
  48. graphicsLayer.ClearGraphics();
  49. break;

  50. }
  51. }
复制代码

大家可以注意一下例子中添加的点符号和曲线符号。只要有足够的想象力,完全可以利用Silverlight定制出非常炫的符号效果来。
原文地址:http://bbs.esrichina-bj.cn/ESRI/thread-44892-1-1.html

 

 

 

本文转自温景良(Jason)博客园博客,原文链接:http://www.cnblogs.com/wenjl520/archive/2009/06/02/1494143.html,如需转载请自行联系原作者

这篇关于ArcGIS API for Silverlight开发入门(4):用户与地理信息之间的桥梁--GraphicsLayer的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/377538

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联