понедельник, 28 февраля 2011 г.

Как подружить Yandex Maps и SQL Server посредством YMapsML

В прошлой статье мы рассматривали организацию отображения на карте Яндекс.Карты объектов из БД MS SQL Server при помощи http-обработчика, написанного на ASP.Net (C#). Однако, это решение не является единственным (кто бы мог подумать J ). В API Яндекс.Карты существует возможность загрузки объектов посредством специального файла в XML формате. Объекты в этом файле описываются специальным YMapsML языком. В общем виде такой файл имеет вид как на листинге 1.

Листинг 1 – Пример YMapsML – файла
<?xml version="1.0" encoding="utf-8"?>
<ymaps:ymaps xmlns:ymaps="http://maps.yandex.ru/ymaps/1.x"
             xmlns:gml="http://www.opengis.net/gml"
             xmlns:repr="http://maps.yandex.ru/representation/1.x"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maps.yandex.ru/schemas/ymaps/1.x/ymaps.xsd">
    <ymaps:GeoObjectCollection>
        <ymaps:style>#anyStyle</ymaps:style>
        <gml:featureMembers>
            <ymaps:GeoObject>
                <gml:description>Описание</gml:description>
                <gml:Point>
                    <gml:pos>37.63 55.75</gml:pos>
                </gml:Point>
            </ymaps:GeoObject>
            
            <ymaps:GeoObject>
                <gml:LineString>
                    <gml:pos>70 55</gml:pos>
                    <gml:pos>80 70</gml:pos>
                    <gml:pos>120 30</gml:pos>
                </gml:LineString>
            </ymaps:GeoObject>
        </gml:featureMembers>
    </ymaps:GeoObjectCollection>
</ymaps:ymaps>

Итак, получим из таблицы (листинг 2) YMapsML – файл, который загрузим на нашу страницу.

Листинг 2 – Таблица с объектами
CREATE TABLE [dbo].[t_Map] (
  [id] int IDENTITY(1, 1) NOT NULL,
  [ObjectName_Var] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [Address_Var] nvarchar(256) COLLATE Cyrillic_General_CI_AS NULL,
  [Longitude_Var] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [Latitude_Var] nvarchar(1) COLLATE Cyrillic_General_CI_AS NULL,
  PRIMARY KEY CLUSTERED ([id])
)

Как видно из листинга 1, файл YMapsML представляет собой довольно сложную иерархическую структуру. Поэтому для преобразования наших реляционных данных (листинг 2) воспользуемся режимом форматирования FOR XML EXPLICIT.
Запрос приведен в листинге 3, я не буду подробно его разбирать, там все довольно понятно, хотя и весьма громоздко.

Листинг 3 – Получение XML из реляционных данных
CREATE PROCEDURE dbo.p_Get_XML
AS
SET NOCOUNT ON
BEGIN
  SELECT
      1 Tag
      , NULL Parent
      , NULL [ymaps:ymaps!1]
      , 'http://maps.yandex.ru/ymaps/1.x' [ymaps:ymaps!1!xmlns:ymaps]
      , 'http://maps.yandex.ru/representation/1.x' [ymaps:ymaps!1!xmlns:repr]
      , 'http://www.opengis.net/gml' [ymaps:ymaps!1!xmlns:gml]
      , 'http://www.w3.org/2001/XMLSchema-instance' [ymaps:ymaps!1!xmlns:xsi]
      , 'http://maps.yandex.ru/schemas/ymaps/1.x/ymaps.xsd' [ymaps:ymaps!1!xsi:schemaLocation]
      , NULL [repr:Representation!2]
      , NULL [repr:View!3]
      , NULL [repr:mapType!4]
      , NULL [ymaps:GeoObjectCollection!5]
      , NULL [ymaps:style!6]
      , NULL [gml:featureMembers!7]
      , NULL [ymaps:GeoObject!8]
      , NULL [gml:name!9]
      , NULL [gml:description!10]
      , NULL [gml:Point!11]
      , NULL [gml:pos!12]
  UNION ALL
  SELECT 2 Tag, 1 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 3 Tag, 2 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 4 Tag, 3 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 5 Tag, 1 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 6 Tag, 5 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 7 Tag, 5 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, NULL, NULL, NULL, NULL
  UNION ALL
  SELECT 8 Tag, 7 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, m.ObjectName_Var, m.Address_Var, NULL, NULL FROM t_Map m
  UNION ALL
  SELECT 9 Tag, 8 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, m.ObjectName_Var, m.Address_Var, NULL, NULL FROM t_Map m
  UNION ALL
  SELECT 10 Tag, 8 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, m.ObjectName_Var, m.Address_Var, NULL, NULL FROM t_Map m
  UNION ALL
  SELECT 11 Tag, 8 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                    NULL, '#customStyle', NULL, NULL, m.ObjectName_Var, m.Address_Var, NULL, NULL FROM t_Map m
  UNION ALL
  SELECT 12 Tag, 11 Parent, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'MAP',
                   NULL, '#customStyle', NULL, NULL, m.ObjectName_Var, m.Address_Var, NULL, m.Longitude_Var + ' ' + m.Latitude_Var
  FROM dbo.t_Map m
  ORDER BY [ymaps:style!6], [gml:description!10], Tag
  FOR XML explicit
END

Как было сказано, запрос громоздкий, но один раз стоит потратить время. Я не стал подробно расписывать раздел repr:Representation, для нас сейчас не это главное.
Теперь надо сделать, чтоб результат данного запроса сохранялся в файл *.xml. Скрипт, который делает это, приведен в листинге 4.


Листинг 4 – Сохранение XML в файл
DECLARE @result int
DECLARE @OutputFileName varchar(150)
DECLARE @cmd varchar( 150)

SET @OutputFileName = 'c:\YMapsML.xml'

SET @cmd = 'BCP "EXEC Site..p_Get_XML" queryout "' + @OutputFileName + '" -w -C1251 -r -T'

EXEC @result = master..xp_cmdshell @cmd

Не забыть проверить, чтоб у файла, куда произойдет выгрузка был открыт доступ и назначены права на редактирование.
Далее файл нужно выложить в интернете так, чтоб он был доступен парсеру Яндекс.Карты. В этом есть ограничение использования данного метода. Например, мой сайт находится в локальной сети, и парсер сюда зайти не может.
Ну и напоследок страничка с подключенным YMapsML (листинг 5).

Листинг 5Подключение YMapsML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Примеры. Визуализация YMapsML.</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script src="http://api-maps.yandex.ru/1.1/index.xml?key=ANpUFEkBAAAAf7jmJwMAHGZHrcKNDsbEqEVjEUtCmufxQMwAAAAAAAAAAAAvVrubVT4btztbduoIgTLAeFILaQ==" type="text/javascript"></script>
    <script type="text/javascript">
        // Создание обработчика для события window.onLoad
        YMaps.jQuery(function () {
            // Создание экземпляра карты и его привязка к созданному контейнеру
            var map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);

            // Установка для карты ее центра и масштаба
            map.setCenter(new YMaps.GeoPoint(55.983161, 54.73794), 12);

            // Создание YMapsML-документа и добавление его на карту
            var ml = new YMaps.YMapsML("http://download1317.mediafire.com/6v198lba6hdg/a4a6lovzelap5b9/1.xml");
            map.addOverlay(ml);

            // Обработчик неудачного создание документа YMapsML
            YMaps.Events.observe(ml, ml.Events.Fault, function (ml, error) {
                alert("Ошибка: " + error);
            });
        });
    </script>
</head>

<body>
    <div id="YMapsID" style="width:600px;height:400px"></div>
</body>

</html>

Удачи!

четверг, 24 февраля 2011 г.

Как подружить Yandex Maps и SQL Server используя http обработчик

Постановка задачи.
Необходимо создать сайт с картой, на которую нанесены объекты.
Объекты заданы таблицей в БД MS SQL Server (листинг 1). Данные объекты необходимо нанести на карту, используя модуль Yandex.Maps от Yandex, т.к. объекты находятся там, где указанный модуль имеет лучшую детализацию, чем другие поставщики карт (например, google). При написании сайта должны использоваться технологии: html, CSS, js, ASP.Net (C#).

Листинг 1 – Таблица с объектами
CREATE TABLE [dbo].[t_Map] (
  [id] int IDENTITY(1, 1) NOT NULL,
  [ObjectName_Var] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [Address_Var] nvarchar(256) COLLATE Cyrillic_General_CI_AS NULL,
  [Longitude_Var] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [Latitude_Var] nvarchar(1) COLLATE Cyrillic_General_CI_AS NULL,
  PRIMARY KEY CLUSTERED ([id])
)

Решение.
Особенность данной задачи заключается в том, что API Яндекс.Карты написано на javascript (js), который является «клиентским» языком. В то же самое время мы должны получать данные с сервера из БД MS SQL Server посредством ASP.Net.
Очевидно, что тут должна помочь технология AJAX (Asynchronous Javascript And Xml - технология для взаимодействия с сервером без перезагрузки страниц), а точнее фреймворк jQuery, который несколько упрощает написание кода.
На стороне сервера будет создан специальный обработчик http, к которому будет обращатся клиент для получения данных из БД.
Итак, начнем. Для начала, создадим заготовку страницы с картой, на которую будут нанесены наши объекты (листинг 2).

Листинг 2Заготовка с картой
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <title>Наша карта</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script src="http://api-maps.yandex.ru/1.1/index.xml?key=ANpUFEkBAAAAf7jmJwMAHGZHrcKNDsbEqEVjEUtCmufxQMwAAAAAAAAAAAAvVrubVT4btztbduoIgTLAeFILaQ==" type="text/javascript"></script>
    <script type="text/javascript" src="http://js.static.yandex.net/jquery/1.3.2/_jquery.js">
    </script>
    <script type="text/javascript">
        // Создание обработчика для события window.onLoad
        YMaps.jQuery(function () {
            // Создание экземпляра карты и его привязка к созданному контейнеру
            var map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);

            // Установка для карты ее центра и масштаба
            map.setCenter(new YMaps.GeoPoint(55.983161, 54.73794), 12);
        })
    </script>
</head>

<body>
    <form id="form1" runat="server">
        <div id="YMapsID" style="width:600px;height:400px"></div>
    </form>
</body>
</html>
Далее напишем запрос к БД, который бы получал необходимые нам данные (Листинг 3).

Листинг 3 – Запрос к БД для получения данных об объектах
CREATE PROCEDURE dbo.p_Get_ObjectData
AS
BEGIN
    SELECT
        m.ObjectName_Var,
        m.Address_Var,
        m.Longitude_Var,
        m.Latitude_Var
    FROM t_Map m
END

Теперь напишем обработчик http (handler), который будет брать данные в БД и отправлять их на клиент (листинг 4).
Листинг 4 – Обрабтчик для получения данных и отправки на клиент
<%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.Configuration;
using System.Data.SqlClient;
using System.Net;
using System.Collections.Generic;

public class Handler : IHttpHandler {
   
    public void ProcessRequest (HttpContext context) {
        //сформируем список, в котором будут находиться наши объекты
        List<object> list = new List<object>();

        string connString = WebConfigurationManager.ConnectionStrings["UfaolapSite"].ConnectionString;
        using (SqlConnection con = new SqlConnection(connString))
        {
            string sql = "exec p_Get_ObjectData";
           
            SqlCommand cmd = new SqlCommand(sql, con);
            con.Open();
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    var data = new
                    {
                        Name = reader.GetString(0),
                        Address = reader.GetString(1),   //адрес
                        Longitude = reader.GetString(2), //долгота
                        Latitude = reader.GetString(3),  //широта
                    };
                    list.Add(data);
                }
            }
            con.Close();
        }

        //сериализуем данный спиок для получения его в формате JSON
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        string output = string.Format("var data = [{0}];", string.Join(",", list.ConvertAll<string>(serializer.Serialize).ToArray()));

        context.Response.ContentType = "text/plain";
        context.Response.Write(output);
        context.Response.End();
        context.Response.Cache.SetNoServerCaching();
    }

    public bool IsReusable {
        get {
            return false;
        }
    }

}
Тут немного поясню. Сначала мы считали данные об объектах – тут все ясно. А дальше мы сериализуем этот список в формат JSON (JavaScript Object Notation – представление объектов JavaScript) с тем, чтоб его удобно и легко было принять на стороне клиента, и выглядеть это будет, как показано на листинге 5.

Листинг 5 – Список объектов в формате JSON
var data = [{"Name":"Памятник Салавату Юлаеву","Address":"Уфа, Заки Валиди, 2","Longitude":"55.925869","Latitude":"54.718393"},{"Name":"УГАТУ","Address":"Уфа, Карла Маркса, 12","Longitude":"55.942434","Latitude":"54.72497}, …];

Теперь можно модифицировать нашу заготовку html (листинг 6).
Листинг 6 – Получение данных на клиенте
<script type="text/javascript">
        // Создание обработчика для события window.onLoad
        YMaps.jQuery(function () {
            // Создание экземпляра карты и его привязка к созданному контейнеру
            var map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);

            // Установка для карты ее центра и масштаба
            map.setCenter(new YMaps.GeoPoint(55.983161, 54.73794), 12);
           
            // Добавим инструменты
            map.addControl(new YMaps.ToolBar());
            map.addControl(new YMaps.Zoom());
            map.addControl(new YMaps.TypeControl());
            map.enableScrollZoom();
           
            $.post('Handler.ashx', '', function(response)
            {
                // распаковываем JSON, в результате чего получаем
                // массив data c данными об объектах
                eval(response);
               
                // Создание стиля для значка метки
                s = new YMaps.Style();
                s.iconStyle = new YMaps.IconStyle();
                s.iconStyle.size = new YMaps.Point(18, 18);
                s.iconStyle.offset = new YMaps.Point(-9, -9);
                s.balloonContentStyle = new YMaps.BalloonContentStyle(
                      new YMaps.Template("<div>$[description]</div>")
                );
                
                for (i in data)
                {
                    var point = new YMaps.GeoPoint(data[i].Longitude, data[i].Latitude);
                    var placemark = new YMaps.Placemark(point, {hasHint: 1, style: s});
                    placemark.name = data[i].Name;
                   
                    placemark.description = data[i].Name + "<br />" + data[i].Address;
                    map.addOverlay(placemark);
                }
            });
        })
    </script>
Вот и все. На выходе получим страницу как на рисунке 1.


Рисунок 1 – Карта с объектами

Как видно, получился довольно простой и удобный механизм «общения» клиента с сервером и БД. Замечу, что с клиента серверу можно передать параметр, который бы мог служить условием для отбора, например. Как это будет выглядеть показано на листинге 7.

Листинг 7 – Передача параметра с запросом
$.post('Handler.ashx', 'parametr = 0', function(response) {…}

В следующей статье рассмотрим формирование YMapsML-файл из БД SQL Server.

Успехов в программировании!