百度地图云麻点之批量上传、实时显示数据篇

      上篇博文你可能用到的百度地图效果(付源码)介绍了几个比较实用的百度地图特效,其中重点介绍了海量数据上传及响应的问题,前端展示可以通过LBS云麻点来展示,通过这个可以解决批量数据Marker响应特慢的性能问题。首先在百度云服务器上建完表之后,我们可以通过后台的管理平台直接把数据传上去,作为我们的初始数据。这部分数据有了之后,接下来要做的就是想办法手动同步数据,更智能一点就是实时同步数据,接下来就带你一步步实现这个过程。

      这次在正文开始之前,我想先做一次吐槽君。最近压力有点儿大,先来发一下牢骚。三人行,则必有我师。无论你职位的高低,工作没有贵贱,当你是新手的时候,你应该要放低姿态,当你是老手的时候,更要放低姿态,因为成熟的麦子都是低着头的。。。上帝为你开了一扇门,就肯定会为你关一扇窗,无论什么时候,都不要觉得你比别人优越多少。当工作中你发现别人犯了错时,你没有义务去纠正别人,帮助别人改正,但是退一万步,你至少不应该冷嘲热讽吧。本是同根生,相煎何太急?程序员又何必为难程序员呢?你可能比别人多了两年的经验,可能比别人多写了些代码,可是你能保证各方面都比别人优秀吗?如果你是老手,多帮帮别人,你漫不经心的一句话可能让别人脱离了迷惘;如果你是新手,努力学习,不要永远做新手!最后,能在一起工作都是缘分,且行且珍惜!

      首先,说说这次同步过程中发现的几个问题及解决方案。1、百度云麻点样式无法自定义,只能使用官方的一些坐标点的样式;2、虽然官方给出的数据是一天请求10万次,但是如果请求频繁的话,会返回错误的数据;3、百度云检索最多只能返回2000条数据,如果需要返回更多数据,可以通过云存储里的查询指定条件的数据列表接口来实现;4、云服务器数据会有缓存,有时候更新不及时;5、上传数据的csv格式里有一列是空的,不知道是干什么用的。      

      接下来开始今天的正文,数据同步这块儿,这里经历了文章开头说到的两个过程,由手动同步升级成自动同步。刚开始做的是傻瓜似的,在页面上放了一个按钮,在按钮点击的时候向handler发送请求,然后去获取本地数据,然后更新到百度云服务器。下图是上传数据需要的接口:

百度地图云麻点之批量上传、实时显示数据篇

     通过API接口可以看到,发送的请求参数也比较少,三个必须参数,一个表ID geotable_id,一个csv文件,即需要上传的数据,另外一个是访问权限ak。到这里我们大致的思路就出来了,首先生成csv格式的数据文件,然后获取配置的ak和geotable_id,然后封装参数,接着发送Post请求。下面我们来一步一步实现这个过程:

      首先生成csv格式的数据文件。这里发生的场景是这样的:当我们最初在百度云服务器建表的时候,表建完之后我们会把最初的数据通过API控制台的数据管理后台把数据批量上传上去,然后本地数据会不断更新,当我们点击页面同步数据按钮的时候,最重要的一个判断就是——需要同步哪些数据。这里可以有两种方案,第一,通过配置表的参数来存本地更新之前最大的一条记录的主键,然后同步的时候去找比这条数据主键更大的数据;第二,直接向百度服务器获取数据,因为百度云存储那边可以对查询出来的数据进行倒叙排序,所以我们只需要按排序字段进行倒叙排列,然后取top1条记录,这样也可以得到最大记录的主键。得到这个主键之后,接下来做的操作就简单了,每次点击同步的时候,对比服务器上存的最大记录主键跟本地数据库当前最大记录主键的大小,如果服务器上的数据比本地小,则有数据需要同步,反之则不。关键代码:

      以下是同步数据的关键代码:

        #region 同步数据
        /// <summary>
        /// 同步数据
        /// </summary>
        /// <param name="context"></param>
        private void HttpToSyncBaiduData(HttpContext context)
        {
            string posturl = "http://api.map.baidu.com/geodata/v3/poi/upload";
            Users userBll = new Users();
            int userId = Globals.SafeInt(context.Request.Form["userId"], -1);
            if (userId > 0)//当前云存储中最大的UserId
            {
                DataSet ds = userBll.GetUserData(userId);
                if (!DataSetTools.DataSetIsNull(ds))
                {
                    string originalName = ExportDataGridToCSV(ds.Tables[0]);
                    NameValueCollection nvc = new NameValueCollection();
                    nvc.Add("geotable_id", geotable_id.ToString());
                    nvc.Add("ak", ak);
                    string contents = HttpUploadFile(posturl, originalName, "poi_list", "application/vnd.ms-excel", nvc);
                    FileManage.DeleteFile(originalName); //删除原文件
                    JsonObject jsonObject = JsonConvert.Import<JsonObject>(contents);
                    context.Response.Write(jsonObject);
                }
                else
                {
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.Put("status", "-1");
                    context.Response.Write(jsonObject);
                }
            }
        } 
        #endregion
View Code

     这里涉及了两个方法,一个是将DataTable转成CSV文件,一个是发送Post请求,以下是代码:  

        #region DataGrid转CSV文件
        /// <summary>
        /// Export the data from datatable to CSV file
        /// </summary>
        /// <param name="grid">DataGrid</param>
        public string ExportDataGridToCSV(DataTable dt)
        {

            string path = null;
            string strFile = "syncUserData" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".csv";
            path = HttpContext.Current.Server.MapPath(strFile);
            using (System.IO.FileStream fs = new FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write))
            {

                using (StreamWriter sw = new StreamWriter(fs, new System.Text.UTF8Encoding()))
                {
                    for (int i = 0; i < dt.Columns.Count - 1; i++)
                    {
                        sw.Write(dt.Columns[i].ColumnName);
                        sw.Write(",");
                    }
                    sw.Write(dt.Columns[dt.Columns.Count - 1].ColumnName);
                    sw.WriteLine("");
                    for (int i = 0; i < dt.Rows.Count; i++)
                    {
                        for (int j = 0; j < dt.Columns.Count - 1; j++)
                        {
                            sw.Write(DelQuota(dt.Rows[i][j].ToString()));
                            sw.Write(",");
                        }
                        sw.Write(DelQuota(dt.Rows[i][dt.Columns.Count - 1].ToString()));
                        sw.WriteLine("");
                    }
                    sw.Flush();
                }

            }
            return path;
        } 
        #endregion

        #region 过滤特殊字符
        /// <summary>
        /// Delete special symbol
        /// </summary>
        /// <param name="str">需要过滤的字符串</param>
        /// <returns></returns>
        public string DelQuota(string str)
        {
            string result = str;
            string[] strQuota = { "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "`", ";", "'", ",", "/", ":", "/,", "<", ">", "?", "|" };
            foreach (string item in strQuota)
            {
                if (result.Contains(item))
                {
                    result = result.Replace(item, "");
                }
            }
            return result;
        } 
        #endregion
View Code

     以下是直接发送Post请求的代码,之前在网上找了很多C#发送Post请求的,用的时候都是各种问题,后来才找到下边这个,确实是可以用:

        #region Post方式提交请求
        /// <summary>
        /// Post方式提交请求
        /// </summary>
        /// <param name="url">请求地址</param>
        /// <param name="file">文件</param>
        /// <param name="paramName">参数名称</param>
        /// <param name="contentType">类型</param>
        /// <param name="nvc">参数集合</param>
        /// <returns></returns>
        public string HttpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc)
        {
            string result = string.Empty;
            string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("
--" + boundary + "
");

            HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(url);
            wr.ContentType = "multipart/form-data; boundary=" + boundary;
            wr.Method = "POST";
            wr.KeepAlive = true;
            wr.Credentials = System.Net.CredentialCache.DefaultCredentials;

            Stream rs = wr.GetRequestStream();

            string formdataTemplate = "Content-Disposition: form-data; name="{0}"

{1}";
            foreach (string key in nvc.Keys)
            {
                rs.Write(boundarybytes, 0, boundarybytes.Length);
                string formitem = string.Format(formdataTemplate, key, nvc[key]);
                byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
                rs.Write(formitembytes, 0, formitembytes.Length);
            }
            rs.Write(boundarybytes, 0, boundarybytes.Length);

            string headerTemplate = "Content-Disposition: form-data; name="{0}"; filename="{1}"
Content-Type: {2}

";
            string header = string.Format(headerTemplate, paramName, file, contentType);
            byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
            rs.Write(headerbytes, 0, headerbytes.Length);

            FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
            byte[] buffer = new byte[10485760];
            int bytesRead = 0;
            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
            {
                rs.Write(buffer, 0, bytesRead);
            }
            fileStream.Close();

            byte[] trailer = System.Text.Encoding.ASCII.GetBytes("
--" + boundary + "--
");
            rs.Write(trailer, 0, trailer.Length);
            rs.Close();

            WebResponse wresp = null;
            try
            {
                wresp = wr.GetResponse();
                Stream stream2 = wresp.GetResponseStream();
                StreamReader reader2 = new StreamReader(stream2);

                result = reader2.ReadToEnd();
            }
            catch (Exception ex)
            {
                if (wresp != null)
                {
                    wresp.Close();
                    wresp = null;
                }
            }
            finally
            {
                wr = null;
            }

            return result;
        } 
        #endregion
View Code

     这里有点奇怪啊,开始我是通过PostMan直接操作的,抓取了提交的参数等信息,然后在后台去拼这个请求,跟PostMan提交的参数完全一致,可是百度返回的提示却一直都是少参数,无奈之下只能放弃最初自己写的,采用了上边的那种方式,不过好在最终目的还是实现了。

     手动同步用了一段时间之后,就发现问题了,每当需要同步数据的时候,都得点一下,这样领导就不高兴了。莫非我要叫个人实时去点这个按钮么?好吧,我做个自动同步的工具。这里说的自动同步工具,其实就是隔一段时间就自己去同步一下数据,隔一段时间去同步一下数据。这里最初也想了两个方案,一个就是做一个页面,隔一段时间向后台发请求,检查是否有可更新的数据,如果有可以更新的数据,就调一下上边的方法。这样做的优势是改动量最小,只需要加一个页面然后写个定时器就搞定了,可是缺点就是如果有多个人同时打开这个页面,数据就会混乱。另一个方案就是建一个Windows服务,定期去跑这个同步数据的代码。这样做的优势是不依赖于IIS,稳定。最终执行的方案也是后者,通过构建windows服务来做这个事情。

     构建Windows服务这里就不赘述了,网上有很多示例,基本上都是一搜一大把。在服务里,我们每隔5分钟去跑一次自动同步的代码,这样就实现了数据自动同步功能。关于百度LBS数据同步这块儿到此结束。这两天正在做一个新用户注册特效和用户下单特效。大致需求就是在以下这个云麻点地图上,一旦有新用户注册,以动态图片显示出他的坐标位置信息,用户下单也是一样,每个用户都是一个云麻点,一旦某个用户下单成功,展示一个烟花绽放的特效。基本功能都做得差不多了,哪天有时间再更新上来!