четверг, 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.

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

10 комментариев:

Sperrowsw комментирует...

Добрый день.
У меня возникла следующая проблема.
Формирование запроса я производил не с помощью хранимой процедуры, а путем составления строки запроса.
Сейчас возникла необходимость фильтровать выводимые на карту данные. Таким образом суть задачи сводится к составлению запроса, удовлетворяющего конкретным условиям.
Условия необходимо получать из DropDownList'a, который расположен на форме.
По сути, вся задача сводится к передаче значения строковой переменной с формы в файл handler.ashx.
Каким образом можно и возможно ли вообще провести эту "операцию".
Заранее благодарен.
С уважением, Павел.

Sperrowsw комментирует...

C вопросом разобрался.
В качестве параметра я передавал строку и при получении строки в hendler'e сбивалась кодировка.
С уважением, Павел.

dedMazDie комментирует...

Вот и отлично!
А то в первом посте проблемы я не увидел, оттого как то загрузился слегка )))

Sperrowsw комментирует...

Я просто не ожидал именно такой проблемы, а отладчиком пройтись, что бы проверить передаваемые значения, возможности не было.

Андрей Р. комментирует...

А как в базе появились вообще данные о памятнике с долготой и широтой?
Как получать эти данные?
Спасибо.

dedMazDie комментирует...

А в чем именно затруднение - получить координаты с карты или в базу запейсать?

Анонимный комментирует...

Уважаемый автор! Подскажите как бы вы решили проблему, если у вас в базе лежало бы 1 000 000 адресов?

Даже, если заранее выгрузить сериализованный текс объектов в файл, то скорее всего проблемы возникнут в yandex, он не помрет от такокго количества точек?

dedMazDie комментирует...

На практике задача с одновременным отображением такого количества объектов встречается редко, мне кажется. Но способы борьбы с большим количеством меток существуют. Например, вот: http://habrahabr.ru/post/145832/

Александр Щербаков комментирует...

Не поможете с примитивной проблемой?

я пытаюсь сделать то же самое на js. из вэбметода возвращается json, он корректно распарсивается, а дальше я в цикле по элементам массива получившегося после распарсивания выполняю:

ymaps.geocode(c[i].addressString, { results: 1 }).then(function (res) { var firstGeoObject = res.geoObjects.get(0); myPlacemark = new ymaps.Placemark(firstGeoObject.geometry.getCoordinates(), {
content: c[i].addressString,
balloonContent: ''
}, { preset: 'twirl#violetIcon' })

проблема заключается в том что я в пресеты плэйсмарка не могу доставить переменные из свойств объекта. (они внутри функции по созданию плэйсмарка не доступны). Ума не приложу что с этим делать. Нужны свойства чтобы их отображать в балуне, в зависимости от них выбирать цвета и т.п.

Александр Щербаков комментирует...

Вот это уже не работает:

content: c[i].addressString

JavaScript runtime error: Unable to get property 'addressString' of undefined or null reference