changeset 10:8705103f074f

Слияние
author cin
date Mon, 21 Aug 2017 18:03:00 +0300
parents 37e9e6bbe87a (current diff) f0035923ff3e (diff)
children 67a5de7581ad 3d4abbce4afc
files
diffstat 48 files changed, 2361 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/BaseLayer.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,44 @@
+define([
+    "implab/text/template-compile",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "ol"],
+    function (compile, declare, lang, ol) {
+        return declare([ol.layer.Layer], {            
+
+            name: null,
+
+            displayName: null,
+
+            identifyResultTemplate: null,
+
+            searchResultTemplate: null,
+
+            constructor: function () {
+                lang.mixin(this, arguments[0]);
+                var identifyCompiledTemplate = null, searchCompiledTemplate = null;
+                if (this.identifyResultTemplate) {
+                    identifyCompiledTemplate = compile(this.identifyResultTemplate);
+                }
+                if (this.searchResultTemplate) {
+                    searchCompiledTemplate = compile(this.searchResultTemplate);
+                }
+            },
+
+            /** Возвращает массив строк, каждая строка - результат поиска  приведенный к шаблонному виду
+                @options {Object}
+                    @str {String} поисковая строка
+                    @bbox {Object} bound box, в рамках которого осуществлять поиск
+            */
+            getSearchResult: function (options) {
+                console.warn("Метод необходимо переопределить для для слоя конкретного типа!");
+            },
+            /** Возвращает массив строк, каждая строка - результат идентификации приведенный к шаблонному виду
+                @options {Object}
+                    @coordinates {Array} массив описывающий координаты точки идентификации                    
+            */
+            getItentifyResult: function (coordinates) {
+                console.warn("Метод необходимо переопределить для для слоя конкретного типа!");
+            }
+        });
+    })
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ClearTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,19 @@
+define (["dojo/_base/declare", "./_OneshotTool", "implab/safe"] , function(declare, _OneshotTool, safe) {
+    return declare([_OneshotTool], {
+        tools : null,
+        
+        constructor : function(opts) {
+            safe.mixin(this,opts, ["tools"]);
+        },
+        
+        invoke : function() {
+            if (this.tools) {
+                this.log("Clear {0} tools", this.tools.length);
+                safe.each(this.tools, function(tool) {
+                    if (tool.clear)
+                        tool.clear();
+                });
+            }
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/DeactivationTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,14 @@
+define(["dojo/_base/declare", "./_ToolBase"],function(declare, _ToolBase) {
+    
+    // Инструмент, выключает текущий активный инструмент
+    // Данный инструмент ничго не делает, но при его активации будет
+    // деактивирован предыдущий инструмент
+    return declare([_ToolBase],  {
+
+        // данный инструмент не может быть активным, поэтому данный метод
+        // переопределяется и возвращает всегда false
+        onActivating : function() {
+            return false;
+        }
+    });  
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/DistanceMeasureTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,63 @@
+define([
+    "dojo/_base/declare",
+    "implab/safe",
+    "./MeasureToolBase",
+    "implab/text/format",
+    "dojo/i18n!./format/nls/units",
+    "ol" ],
+
+function(declare, safe, MeasureToolBase, format, units, ol) {
+    return declare([ MeasureToolBase ], {
+
+        isGeodesic : true,
+
+        units : 'metric',
+        
+        constructor : function(opts) {
+            if (opts)
+                safe.mixin(this,opts,["isGeodesic", "units"]);
+        },
+
+        _createDraw : function(source) {
+            return new ol.interaction.Draw(
+                {
+                    source : source,
+                    type : "LineString",
+                    style : this.drawStyle
+                });
+        },
+
+        _formatTooltip : function(sketch, proj) {
+            var length = 0;
+            if (this.isGeodesic) {
+                var points = sketch.getGeometry().getCoordinates();
+                var t = ol.proj.getTransform(proj, "EPSG:4326");
+                for (var i = 0; i < points.length - 1; i++) {
+                    length += this.wgs84Sphere.haversineDistance(
+                        t(points[i]),
+                        t(points[i + 1]));
+                }
+            } else {
+                length = sketch.getGeometry().getLength();
+            }
+
+            var mpu, unitName;
+            switch (this.units) {
+                case "nautical":
+                    mpu = 1852;
+                    unitName = units.nmiles;
+                    break;
+                default:
+                    mpu = 1000;
+                    unitName = units.kilometers;
+            }
+
+            if (length >= mpu) {
+                return format("{0:#.0#}{1}", length / mpu, unitName);
+            } else {
+                return format("{0:#0.0#}{1}", length, units.meters);
+            }
+        }
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/DynamicStyle.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,119 @@
+define([ "./declare-style", "dojo/_base/declare", "ol", "implab/safe" ],
+
+function(declare, dojoDeclare, ol, safe) {
+    return declare([], {
+        _cache : null,
+        _getKey : null,
+        _getZoom : null,
+        
+        defaultStyle : null,
+        
+        style : null,
+        
+        styleFunction : null,
+        
+        filter : null,
+
+        constructor : function(opts) {
+            if (opts)
+                dojoDeclare.safeMixin(this, opts);
+
+            this._cache = {};
+
+            if (this.zoom) {
+                if (this.zoom instanceof Function) {
+                    this._getZoom = this.zoom;
+                } else {
+                    var levels = [], max = "max";
+                    for ( var p in this.zoom) {
+                        if (safe.isNumber(this.zoom[p]))
+                            levels.push({
+                                name : p,
+                                zoom : Number(this.zoom[p])
+                            });
+                        else if (this.zoom[p] == "max")
+                            max = p;
+                    }
+
+                    levels.sort(function(x, y) {
+                        return x.zoom - y.zoom;
+                    });
+
+                    this.zoomMax = max;
+                    this.zoomLevels = levels;
+
+                    this._getZoom = function(z) {
+                        for (var i = 0; i < levels.length; i++) {
+                            if (z <= levels[i].zoom)
+                                return levels[i].name;
+                        }
+                        return max;
+                    };
+                }
+            } else {
+                this._getZoom = function(z) {
+                    return "max";
+                };
+            }
+
+            if (this.key) {
+                if (this.key instanceof Function) {
+                    this._getKey = this.key;
+                } else if (typeof this.key === "string") {
+                    this._getKey = function(ft) {
+                        return ft.get(this.key);
+                    };
+                } else {
+                    this._getKey = function(ft, z) {
+                        var k = this.key[z];
+                        if (k instanceof Function)
+                            return k.call(this, ft, z);
+                        else if (typeof k == "string")
+                            return ft.get(k);
+                        else
+                            return this.defaultStyle;
+                    };
+                }
+            } else {
+                this._getKey = function() {
+                    return this.defaultStyle;
+                };
+            }
+
+            if (this.style) {
+                if (this.style instanceof Function) {
+                    this._style = this.style;
+                } else {
+                    this._style = function(ft, res, key, zoom) {
+                        var s = this.style[zoom];
+                        return s && (s instanceof Function) ? s.apply(
+                            this,
+                            arguments) : s;
+                    };
+                }
+            }
+        },
+
+        getFeatureStyle : function(ft, res) {
+            safe.argumentNotNull(ft, "ft");
+            
+            if (this.filter && this.filter(ft) === false)
+                return null;
+
+            var z = this._getZoom(res);
+            var k = this._getKey(ft, z);
+
+            var cid = [ k, z ].join('-');
+
+            var style = this._cache[cid];
+            if (!style) {
+                style = this._style ? this._style(ft, res, k, z) : null;
+                this._cache[cid] = style;
+            }
+
+
+            return safe.isNull(style) || style instanceof Array ? style : [ style ];
+        }
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/IdentificationTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,246 @@
+define([
+        "dijit/layout/ContentPane",
+        "dojo/_base/declare",
+        "dojo/Deferred",
+        "dojo/dom-construct",
+        "dojo/dom-class",
+        "dojo/on",
+        "dojo/promise/all",
+        "dojo/when",
+        "implab/safe",
+        "ol",
+        "./listen",
+        "./IdentifyGroup",
+        "./_ToolBase",
+        "./PopupContainer"
+    ],
+
+    function (
+        ContentPane,
+        declare,
+        Deffered,
+        domConstruct,
+        domClass,
+        on,
+        promiseAll,
+        when,
+        safe,
+        ol,
+        listen,
+        IdentifyGroup,
+        _ToolBase,
+        PopupContainer) {
+        return declare([_ToolBase], {
+            /**
+             * массив обработчиков openLayers, которые необходимо удалить при
+             * деактивации
+             */
+            _handlers: null,
+            /**
+             * widget карты
+             */
+            _map: null,
+            /**
+             * openLayers map
+             */
+            _olMap: null,
+            /**
+             * Массив overlays содержащих popupContainers
+             */
+            _popupOverlays: null,
+            /**
+             * Массив popups контейнеров
+             */
+            _popupContainers: null,
+            /**
+             * Режим работы инструмента идентификации
+             * 
+             * @value {String} "single"||"multiple"
+             */
+            mode: null,
+            /**
+             * Режимы
+             */
+            appManager: null,
+
+            constructor: function (options) {
+                safe.argumentNotNull(options, "options");
+                safe.argumentNotNull(options.map, "options.map");
+                safe.argumentNotNull(options.appManager, "options.appManager");
+
+                this._map = options.map;
+                this._olMap = options.map.olMap;
+                this._popupContainers = [];
+                this._popupOverlays = [];
+                this._handlers = [];
+
+                this.appManager = options.appManager;
+                this.mode = options.mode || "single";
+            },
+            /** 
+             */
+            createPopupContent: function (groupWidgets) {
+                var contentWidget = new ContentPane();
+                groupWidgets.forEach(function (groupWidget) {
+                    if (groupWidget && !groupWidget.isEmpty()) {
+                        contentWidget.addChild(groupWidget);
+                    }
+                });
+                return contentWidget;
+            },
+
+            /**
+             * Возвращает обещание на получение объекта с результатом идентификации
+             * по всем видимым режимам и информационным слоям @ options {Object} @ pixel
+             * {Object} - информация о точке идентификации @ coordinate {Array} -
+             * координаты точки иденификации
+             */
+            getGroupsFromVisibleModules: function (options) {
+                var promises = [];
+                var modules = this.appManager.getVisibleModules();
+                modules.forEach(function (module) {
+                    promises.push.apply(promises, module
+                        .getIdentifyResult(options));
+                });
+
+                return promiseAll(promises).then(
+                    function (results) {
+                        console.log("promise all groups = ", results);
+                        return results;
+                    });
+            },
+
+            identifyPosition: function (position) {
+                var me = this, i;
+                var popupContainer = null,
+                    popupOverlay = null;
+                if (me.mode == "multiple") {
+                    // TODO: создать popupContainer и popupOverlay
+                    popupContainer = new PopupContainer({
+                        map: me._olMap
+                    });
+                    on(popupContainer, "close", function () {
+                        var index = me._popupContainers.indexOf(me);
+                        me._popupContainers.splice(index, 1);
+
+                    });
+                    me._popupContainers.push(popupContainer);
+
+                    popupOverlay = new ol.Overlay({
+                        element: popupContainer.domNode,
+                        autoPan: true,
+                        autoPanAnimation: {
+                            duration: 250
+                        }
+                    });
+                    me._popupOverlays.push(popupOverlay);
+                    me._olMap.addOverlay(popupOverlay);
+
+                } else {
+                    if (me._popupContainers.length > 0) {
+                        // Берем первый
+                        popupContainer = me._popupContainers[0];
+                        // Все остальные удалить
+                        if (me._popupContainers.length > 1) {
+                            for (i = 1; i < me._popupContainers.length; i++) {
+                                me._popupContainers[i].destroyRecursive();
+                            }
+                            me._popupContainers.splice(
+                                1,
+                                me._popupContainers.length - 1);
+                        }
+                    } else {
+                        // Создаем новый
+                        popupContainer = new PopupContainer({
+                            map: me._olMap
+                        });
+                        on(popupContainer, "close", function () {
+                            var index = me._popupContainers.indexOf(me);
+                            me._popupContainers.splice(index, 1);
+
+                        });
+                        me._popupContainers.push(popupContainer);
+                    }
+                    if (me._popupOverlays.length > 0) {
+                        // Берем первый и помещаем в него popup
+                        popupOverlay = me._popupOverlays[0];
+                        popupOverlay.setElement(popupContainer.domNode);
+                        // Все остальные удалить
+                        if (me._popupOverlays.length > 1) {
+                            for (i = 1; i < me._popupOverlays.length; i++) {
+                                me._olMap.removeOverlay(me._popupOverlays[i]);
+                            }
+                            me._popupOverlays.splice(
+                                1,
+                                me._popupOverlays.length - 1)
+                        }
+                    } else {
+                        // Создаем новый и помещаем в него popup
+                        popupOverlay = new ol.Overlay({
+                            element: popupContainer.domNode,
+                            autoPan: true,
+                            autoPanAnimation: {
+                                duration: 250
+                            }
+                        });
+                        me._popupOverlays.push(popupOverlay);
+                        me._olMap.addOverlay(popupOverlay);
+                    }
+                }
+
+                popupContainer.destroyDescendants();
+
+                popupOverlay.setPosition(position.coordinate);
+
+                popupContainer.showOverlay();
+
+                when(me.getGroupsFromVisibleModules(position), function (data) {
+                    var contentWidget = me.createPopupContent(data);
+                    popupContainer.show(contentWidget, "only");
+                    popupContainer.hideOverlay();
+                });
+            },
+            /**
+             * Скрыть все popups
+             */
+            hideAllPopups: function () {
+                var me = this, i;
+                for (i = 0; i < this._popupContainers.length; i++) {
+                    this._popupContainers[i].destroyRecursive();
+                }
+                this._popupContainers.splice(0, this._popupContainers.length);
+
+                for (i = 0; i < this._popupOverlays.length; i++) {
+                    this._olMap.removeOverlay(this._popupOverlays[i]);
+                }
+                this._popupOverlays.splice(0, this._popupOverlays.length)
+            },
+
+            onActivating: function () {
+                var me = this;
+                // Обработчик для события "singleclick" по карте
+                var handler = listen(this._olMap, 'singleclick', function (evt) {
+                    if (evt.originalEvent.ctrlKey) {
+                        me.mode = "multiple";
+                    } else {
+                        me.mode = "single";
+                    }
+                    me.identifyPosition({
+                        pixel: evt.pixel,
+                        coordinate: evt.coordinate
+                    });
+                });
+                this._handlers.push(handler);
+            },
+
+            onDeactivating: function () {
+                var me = this;
+                me._handlers.forEach(function (handler) {
+                    if (handler.remove)
+                        handler.remove();
+
+                });
+                this.hideAllPopups();
+            },
+        })
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/IdentifyGroup.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,89 @@
+define(
+    [
+        "implab/safe",
+        'dijit/_WidgetBase',
+        'dijit/_TemplatedMixin',
+        "dijit/_WidgetsInTemplateMixin",
+        "dijit/layout/_LayoutWidget",
+        "dijit/layout/ContentPane",
+        "dojo/_base/declare",
+        "dojo/dom-construct",
+        "dojo/on",
+        "ol3/IdentifyItem" ],
+    function(
+        safe,
+        _WidgetBase,
+        _TemplatedMixin,
+        _WidgetsInTemplateMixin,
+        _LayoutWidget,
+        ContentPane,
+        declare,
+        domConstruct,
+        on,
+        IdentifyItem) {
+
+        return declare(
+            [ _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin ],
+            {
+
+                /**
+                 */
+                _empty : true,
+                /**
+                 */
+                layerName : null,
+                /**
+                 * Шаблон всего widget
+                 */
+                templateString : "<div class='identify-item-group'><div data-dojo-attach-point='identifyItemGroupTitle' class='identify-item-group-title'></div><div class='identify-item-group-content-container' data-dojo-attach-point='containerNode'></div></div>",
+
+                itemTemplate : "",
+
+                title : null,
+
+                /**
+                 * Метод из widget.lifecycle
+                 */
+                postCreate : function() {
+                    var me = this;
+                    if (typeof this.title == "string") {
+                        this.identifyItemGroupTitle.innerHTML = me.title;
+                    } else if (me.title && me.title.placeAt) {
+                        me.title.placeAt(me.identifyItemGroupTitle, "only");
+                    } else {
+                        domConstruct.place(
+                            me.title,
+                            me.identifyItemGroupTitle,
+                            "only");
+                    }
+                    on(this.domNode, "click", function() {
+                        if ("function" == typeof me.callback) {
+                            me.callback();
+                        }
+                    });
+                },
+
+                addItem : function(options) {
+                    safe.argumentNotNull(options, "options");
+                    safe.argumentNotNull(options.model, "options.model");
+
+                    if (options.model) {
+
+                        var item = new IdentifyItem({
+                            title : options.title || this.itemTemplate,
+                            model : options.model,
+                            callback : options.callback
+                        });
+                        this.addChild(item);
+                        this._empty = false;
+                    } else {
+                        console
+                            .error("Не задано необходимое свойство layerFeature");
+                    }
+                },
+
+                isEmpty : function() {
+                    return this._empty;
+                }
+            });
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/IdentifyItem.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,62 @@
+define([
+    'dijit/_WidgetBase',
+    'dijit/_TemplatedMixin',
+    "dijit/Tooltip",
+    "dojo/_base/declare",
+    "dojo/date/locale",
+    "dojo/dom-construct",
+    "dojo/on" ], function(_WidgetBase, _TemplatedMixin, Tooltip, declare,
+    dateLocale, domConstruct, on) {
+    var empty = {};
+    return declare([ _WidgetBase, _TemplatedMixin ], {
+
+        callback : null,
+
+        dateLocale : dateLocale,
+
+        baseClass : 'identify-item',
+
+        feature : null,
+
+        model : empty,
+
+        title : null,
+
+        templateString : "<div class='identify-item'></div>",
+
+        constructor : function(options) {
+            option = options || {};
+            if (options.title)
+                this.title = options.title;
+            if (options.model)
+                this.model = options.model;
+            if (options.callback)
+                this.callback = options.callback;
+
+        },
+
+        /**
+         * Метод из widget.lifecycle
+         */
+        postCreate : function() {
+            var me = this;
+
+            var content = me.title instanceof Function ? me.title(me)
+                : me.title;
+
+            if (typeof content == "string") {
+                me.domNode.innerHTML = content;
+            } else if (content && content.placeAt) {
+                content.placeAt(me.domNode, "only");
+            } else {
+                domConstruct.place(content, me.domNode, "only");
+            }
+
+            on(me.domNode, "click", function() {
+                if (typeof me.callback == "function") {
+                    me.callback(me.model);
+                }
+            });
+        },
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ImageLayer.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+define(["ol"], function(ol) {
+    return ol.layer.Image
+})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ImageWMSSource.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+define(["ol"], function(ol) {
+    return  ol.source.ImageWMS;
+})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/LayerCheckBox.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,49 @@
+define([
+    "dojo/_base/declare",
+    "dijit/_WidgetBase",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dojo/text!./resources/LayerCheckBoxTemplate.html",
+    "dijit/form/CheckBox" ], function(declare, _WidgetBase, _TemplatedMixin,
+    _WidgetsInTemplateMixin, templateString) {
+    return declare([ _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {
+        templateString : templateString,
+        labelNode : null,
+        checkBox : null,
+
+        label : "",
+        _setLabelAttr : {
+            node : "labelNode",
+            type : "innerHTML"
+        },
+
+        name : "layer",
+
+        _layer : null,
+
+        constructor : function(options) {
+            options = options || {};
+
+            if (!options.layer)
+                throw new Error("The layer is required");
+
+            this._layer = options.layer;
+            this.label = options.layer.get("label") || "unnamed";
+        },
+
+        postCreate : function() {
+            var me = this;
+            me.inherited(arguments);
+
+            me.checkBox.set('name', me.name);
+            me.checkBox.set('value', me._layer.getVisible());
+            this.checkBox.on("change", function(value) {
+                me._changed(value);
+            });
+        },
+
+        _changed : function(visible) {
+            this._layer.setVisible(visible);
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/LayerRadioButton.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,50 @@
+define([
+    "dojo/_base/declare",
+    "dijit/_WidgetBase",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dojo/text!./resources/LayerRadioButtonTemplate.html",
+    "dijit/form/RadioButton" ], function(declare, _WidgetBase, _TemplatedMixin,
+    _WidgetsInTemplateMixin, templateString) {
+    return declare([ _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {
+        templateString : templateString,
+
+        labelNode : null,
+        radioButton : null,
+
+        label : "",
+        _setLabelAttr : {
+            node : "labelNode",
+            type : "innerHTML"
+        },
+
+        name : "layers",
+
+        _layer : null,
+
+        constructor : function(options) {
+            options = options || {};
+
+            if (!options.layer)
+                throw new Error("The layer is required");
+
+            this._layer = options.layer;
+            this.label = options.layer.get("label") || "unnamed";
+        },
+
+        postCreate : function() {
+            var me = this;
+            me.inherited(arguments);
+
+            me.radioButton.set('name', me.name);
+            me.radioButton.set('value', me._layer.getVisible());
+            this.radioButton.on("change", function(value) {
+                me._changed(value);
+            });
+        },
+
+        _changed : function(visible) {
+            this._layer.setVisible(visible);
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/LayerSwitcher.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,81 @@
+define([
+    "dojo/_base/declare",
+    "require",
+    "dijit/_WidgetBase",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dojo/text!./resources/LayerSwitcherTemplate.html",
+    "./LayerCheckBox",
+    "./LayerRadioButton",
+    "app/view/Container" ],
+
+function(declare, require, _WidgetBase, _TemplatedMixin,
+    _WidgetsInTemplateMixin, templateString, LayerCheckBox, LayerRadioButton) {
+
+    return declare([ _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin ], {
+        templateString : templateString,
+        requireScope : require,
+        _layers : null,
+        _map : null,
+
+        infoLayersLabel : "Layers",
+        _setInfoLayersLabelAttr : {
+            node : "infoLayersLabelNode",
+            type : "innerHTML"
+        },
+
+        baseLayersLabel : "Base",
+        _setBaseLayersLabelAttr : {
+            node : "baseLayersLabelNode",
+            type : "innerHTML"
+        },
+
+        constructor : function(options) {
+            options = options || {};
+
+            if (!options.map)
+                throw new Error("The map is required");
+
+            this._map = options.map;
+
+            if (options.layers && options.layers instanceof Array)
+                this._layers = options.layers;
+            else
+                this._layers = [];
+
+            // this.baseLayersLabel = "Base";
+
+        },
+
+        postCreate : function() {
+            this.inherited(arguments);
+
+            var pending = [];
+
+            for ( var i in this._layers) {
+                if (this._layers[i].get("layerType") != "base")
+                    pending.push(this._layers[i]);
+                else
+                    this._addLayer(this._layers[i]);
+            }
+
+            for ( var i in pending)
+                this._addLayer(pending[i]);
+        },
+
+        _addLayer : function(layer) {
+            this._map.addLayer(layer);
+
+            if (layer.get("layerType") === "base") {
+                this.baseLayersContainer.addChild(new LayerRadioButton({
+                    layer : layer
+                }));
+            } else {
+                this.infoLayersContainer.addChild(new LayerCheckBox({
+                    layer : layer
+                }));
+            }
+        },
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/Map.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,176 @@
+define([
+    "dijit/registry",
+    "dojo/_base/declare",
+    "dijit/_WidgetBase",
+    "dojo/dom-construct",
+    "./PopupContainer",
+    "dojo/_base/array",
+    "ol",
+    "ol3/listen",
+    "dojo/Deferred",
+    "implab/safe"
+], function (registry, declare, _WidgetBase, dom, PopupContainer, array, ol, listen, Deferred, safe) {
+
+    return declare([_WidgetBase], {
+        popupOverlays: null,
+        olMap: null,
+
+        _pending: null,
+
+        constructor: function () {
+            this._pending = {};
+        },
+
+        buildRendering: function () {
+            this.domNode = dom.create("div");
+        },
+
+        postCreate: function () {
+            this.inherited(arguments);
+            this.popupOverlays = {};
+            this.olMap = new ol.Map({
+                target: this.domNode,
+                layers: this.layers,
+                view: this.view,
+                controls: this.controls || [],
+            });
+        },
+
+        getProjection : function() {
+            return this.view.getProjection();
+        },
+
+        addInteraction: function (value) {
+            this.olMap.addInteraction(value);
+        },
+
+        addLayer: function (layer) {
+            if (layer) {
+                if (layer.get("layerType") == "base")
+                    this.olMap.getLayers().insertAt(0, layer);
+                else
+                    this.olMap.addLayer(layer);
+            }
+        },
+
+        removeLayer: function(layer) {
+            this.olMap.removeLayer(layer);
+        },
+
+        startup: function () {
+            this.inherited(arguments);
+
+            this.olMap.updateSize();
+        },
+
+        showPopup: function (contentWidget, position, opts) {
+            // Скрыть popups указанной в opts.role роли, если (opts.hint ==
+            // "replace")
+            // Если не задан opts или opta.hint скрывать все popup
+
+            var me = this;
+
+            if ((!opts) || (!opts.hint)) {
+                this.closeAllPopups();
+            } else if ((opts.hint) && (opts.hint == "replace")) {
+                if (opts.role) {
+                    this.closePopupsByRole(opts.role);
+                } else {
+                    this.closeAllPopups();
+                }
+            }
+            var overlay = new ol.Overlay({});
+
+            if (opts && (opts.role)) {
+                if (this.popupOverlays[opts.role]) {
+                    this.popupOverlays[opts.role].push(overlay);
+                } else {
+                    this.popupOverlays[opts.role] = [overlay];
+                }
+            }
+
+            // Отображение popUp start
+            this.olMap.addOverlay(overlay);
+            var popup = new PopupContainer({
+                overlay: overlay,
+                map: this.olMap,
+                onClose: function () {
+                    // registry.byNode(overlay.getElement()).destroyRecursive();
+                    array.forEach(me.popupOverlays[opts.role], function (o) {
+                        if (o === overlay) {
+                            var index = me.popupOverlays[opts.role].indexOf(o);
+                            if (index > -1) {
+                                me.popupOverlays[opts.role].splice(index, 1);
+                            }
+                        }
+                    });
+                }
+            });
+            overlay.setElement(popup.domNode);
+            popup.show(contentWidget);
+            overlay.setPosition(position);
+            popup.hideOverlay();
+            // end
+            return popup;
+        },
+
+        closeAllPopups: function () {
+            var overlays = this.olMap.getOverlays();
+            overlays.forEach(function (elenemt, index) {
+                registry.byNode(elenemt.getElement()).destroyRecursive();
+            }, this);
+            this.popupOverlays = {};
+        },
+
+        closePopupsByRole: function (role) {
+            if (this.popupOverlays[role]) {
+                array.forEach(this.popupOverlays[role], function (overlay) {
+                    registry.byNode(overlay.getElement()).destroyRecursive();
+                });
+                this.popupOverlays[role] = [];
+            }
+        },
+
+        /**
+         * Подписывается на событие карты
+         * 
+         * @param {String}
+         *            name Имя события
+         * @param {function(evt)}
+         *            filter Фильтр того, что событие нужное
+         * 
+         * @returns {ol.ObjectEvent | ol.MapBroeserEvent | ol.MapEvent} Событие
+         *          на которое подписались.
+         */
+        awaitMapEvent: function (name, filter) {
+            safe.argumentNotEmptyString(name, "name");
+            var map = this.olMap,
+                handle, d, me = this;
+
+            if (me._pending[name])
+                throw new Error("An event is already pending: " + name);
+
+            me._pending[name] = d = new Deferred(function () {
+                handle.remove();
+            });
+
+            handle = listen.once(map, name, function (evt) {
+                if (!filter || filter(evt))
+                    d.resolve(evt);
+            });
+
+            return d.then(function (evt) {
+                delete me._pending[name];
+                return evt;
+            }, function (err) {
+                delete me._pending[name];
+                throw err;
+            });
+        },
+
+        cancelPendingEvents: function () {
+            for (var name in this._pending)
+                this._pending[name].cancel();
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/MeasureToolBase.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,181 @@
+define([
+    "dojo/_base/declare",
+    "implab/safe",
+    "ol",
+    "./listen",
+    "./_ToolBase",
+    "dojo/dom-construct",
+    "dojo/dom-class"
+], function (declare, safe, ol, listen, _ToolBase, dconstruct, dclass) {
+    return declare([_ToolBase], {
+        _draw: null,
+
+        drawStyle: new ol.style.Style({
+            fill: new ol.style.Fill({
+                color: 'rgba(255, 255, 255, 0.2)'
+            }),
+            stroke: new ol.style.Stroke({
+                color: 'rgba(230, 126, 34, 0.7)',
+                lineDash: [10, 10],
+                width: 2
+            }),
+            image: new ol.style.Circle({
+                radius: 5,
+                stroke: new ol.style.Stroke({
+                    color: 'rgba(0, 0, 0, 0.7)'
+                }),
+                fill: new ol.style.Fill({
+                    color: 'rgba(255, 255, 255, 0.2)'
+                })
+            })
+        }),
+
+        vectorStyle: new ol.style.Style({
+            fill: new ol.style.Fill({
+                color: 'rgba(255, 255, 255, 0.2)'
+            }),
+            stroke: new ol.style.Stroke({
+                color: '#ffcc33',
+                width: 2
+            }),
+            image: new ol.style.Circle({
+                radius: 7,
+                fill: new ol.style.Fill({
+                    color: '#ffcc33'
+                })
+            })
+        }),
+
+        _map: null,
+
+        _olMap: null,
+
+        _measureTooltipElement: null,
+        /**
+         * Overlay to show the measurement.
+         * 
+         * @type {ol.Overlay}
+         */
+        _measureTooltip: null,
+
+        _pointermoveKey: null,
+
+        _sketch: null,
+
+        _overlays: null,
+
+        _vector: null,
+
+        wgs84Sphere: new ol.Sphere(6378137),
+
+        constructor: function (options) {
+            safe.argumentNotNull(options, "options");
+            safe.argumentNotNull(options.map, "map");
+
+            this._map = options.map;
+            this._olMap = options.map.olMap;
+            this._overlays = [];
+        },
+
+        init: function () {
+            if (this._draw)
+                return;
+
+            var source = new ol.source.Vector();
+
+            this._vector = new ol.layer.Vector({
+                source: source,
+                style: this.vectorStyle
+            });
+            this._map.addLayer(this._vector);
+
+            this._draw = this._createDraw(source);
+            this._draw.on('drawstart', this._onDrawStart, this);
+            this._draw.on('drawend', this._onDrawEnd, this);
+
+        },
+
+        onActivating: function () {
+            this.init();
+
+            this._pointermoveKey = listen(this._olMap, 'pointermove', safe.delegate(this, "_onPointerMove"));
+            this._olMap.addInteraction(this._draw);
+
+            return this.inherited(arguments);
+        },
+
+        onDeactivating: function () {
+            // отключаем рисование и получение сообщений
+            if (this._pointermoveKey)
+                this._pointermoveKey.remove();
+            this._olMap.removeInteraction(this._draw);
+
+            // если был активен инструмент
+            if (this._sketch) {
+                // убиваем подсказку и сбрасываем текущее рисование
+                this._sketch = null;
+                this._measureTooltipElement = null;
+                this._olMap.removeOverlay(this._measureTooltip);
+            }
+
+            return this.inherited(arguments);
+        },
+
+        clear: function () {
+            var me = this;
+            me.log("clear");
+            if (me._vector) {
+                me._vector.getSource().clear();
+                me._overlays.forEach(function (x) {
+                    me._olMap.removeOverlay(x);
+                });
+            }
+        },
+
+        _createMeasureTooltip: function () {
+            this._measureTooltipElement = dconstruct.create("div", {
+                "class": 'tooltip poisk-measure-tooltip'
+            });
+            this._measureTooltip = new ol.Overlay({
+                element: this._measureTooltipElement,
+                offset: [0, -15],
+                positioning: 'bottom-center'
+            });
+            this._olMap.addOverlay(this._measureTooltip);
+
+        },
+
+        _onDrawStart: function (evt) {
+            this._sketch = evt.feature;
+            this._createMeasureTooltip();
+        },
+
+        _onDrawEnd: function (evt) {
+
+            this._measureTooltip.setOffset([0, -7]);
+
+            dclass.remove(this._measureTooltipElement, "tooltip");
+            dclass.add(this._measureTooltipElement, "tooltip-static");
+
+            this._overlays.push(this._measureTooltip);
+
+            this._sketch = null;
+            this._measureTooltip = null;
+            this._measureTooltipElement = null;
+        },
+
+        _onPointerMove: function (evt) {
+            if (this._sketch && !evt.dragging) {
+                this._measureTooltip.setPosition(evt.coordinate);
+                this._measureTooltipElement.innerHTML = this._formatTooltip(
+                    this._sketch,
+                    this._olMap.getView().getProjection());
+            }
+        },
+
+        _formatTooltip: function (sketch, proj) {
+
+        }
+    });
+
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/OSMSource.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+define(["ol"], function(ol) {
+    return  ol.source.OSM;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/OlTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,21 @@
+define(["dojo/_base/declare", "dojo/_base/lang", "implab/safe", "dojo/dom-class", "dojo/dom-construct", "dijit/_TemplatedMixin", "dijit/_WidgetBase"],
+    function (declare, lang, safe, domClass, domConstruct, _TemplatedMixin, _WidgetBase) {
+    return declare([_WidgetBase, _TemplatedMixin], {
+        map : null,
+        control : null,
+        elementName : "div",
+        
+        constructor: function () {
+            lang.mixin(this, arguments[0]);
+            safe.argumentNotNull(this.map, "map");
+            safe.argumentNotNull(this.control, "control");
+        },
+        
+        buildRendering : function() {
+            this.domNode = domConstruct.create(this.elementName);
+            domClass.add(this.domNode, this.baseClass);
+            this.control.setTarget(this.domNode);
+            this.control.setMap(this.map.olMap || this.map);
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/PopupContainer.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,60 @@
+define([       
+    "dijit/_WidgetBase",
+    "dijit/_TemplatedMixin",
+    "dijit/_WidgetsInTemplateMixin",
+    "dijit/_Container",
+    "dojo/_base/declare",
+    "dojo/Evented",
+    "dojo/dom-class",
+    "dojo/on",
+    "dojo/text!./resources/PopupContainerTemplate.html",
+    "ol"
+],
+    function (_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _Container, declare, Evented, domClass, on, templateString, ol) {
+        return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _Container/*, Evented*/], {
+
+            templateString: templateString,
+            
+            overlay: null,
+            
+            map : null,
+
+            constructor: function (options) {
+                options = options || {};                
+            },
+
+            postCreate: function () {
+                var me = this;                
+                this.inherited(arguments);
+                on(this.popupCloser, "click", function () {
+                    me.destroyRecursive();
+                    me.onClose();
+                });
+            },
+                        
+            show: function (widget, opts) {
+                opts = opts || "only";
+                if (widget) {
+                    widget.placeAt(this.popupContent, opts);
+                }                
+            },
+
+            hideOverlay: function () {
+                domClass.add(this.popupOverlay, "hidden")
+            },
+
+            showOverlay: function () {
+                domClass.remove(this.popupOverlay, "hidden")
+            },
+
+            destroy : function() {
+                this.map.removeOverlay(this.overlay);                
+                this.inherited(arguments);
+            },
+
+            onClose: function () {                
+                this.emit("close");
+            }
+
+        });
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/SquareMeasureTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,61 @@
+define([
+    "dojo/_base/declare",
+    "implab/safe",
+    "./MeasureToolBase",
+    "implab/text/format",
+    "dojo/i18n!./format/nls/units",
+    "ol" ],
+
+function(declare, safe, MeasureToolBase, format, units, ol) {
+    return declare([ MeasureToolBase ], {
+
+        isGeodesic : true,
+        
+        units : 'metric',
+        
+        constructor : function(opts) {
+            if (opts)
+                safe.mixin(this,opts,["isGeodesic", "units"]);
+        },
+
+        _createDraw : function(source) {
+            return new ol.interaction.Draw({
+                source : source,
+                type : "Polygon",
+                style : this.drawStyle
+            });
+        },
+
+        _formatTooltip : function(sketch, proj) {
+            var area;
+            if (this.isGeodesic) {
+                                var geom = sketch.getGeometry().clone().transform(proj, 'EPSG:4326');
+                var coordinates = geom.getLinearRing(0).getCoordinates();
+                area = Math.abs(this.wgs84Sphere.geodesicArea(coordinates));
+            } else {
+                area = sketch.getGeometry().getArea();
+            }
+            
+            var mpu, unitName;
+            switch (this.units) {
+                case "nautical":
+                    mpu = 1852*1852;
+                    unitName = units.nmiles2;
+                    break;
+                default:
+                    mpu = 1852*1852;
+                    unitName = units.kilometers2;
+            }
+
+            if (area > mpu/10) {
+                return format(
+                    "{0:#0.##} {1}",
+                    area / mpu,
+                    unitName);
+            } else {
+                return format("{0:#0.##} {1}", area, units.meters2);
+            }
+        }
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/TileLayer.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+define(["ol"], function(ol) {
+    return ol.layer.Tile;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/TileWMSSource.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+define(["ol"], function(ol) {
+    return  ol.source.TileWMS;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ToolBoxController.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,43 @@
+define([
+    "dojo/_base/declare",
+    "implab/safe",
+    "implab/components/ActivationController",
+    "./ToolType" ],
+
+function(declare, safe, ActivationController, ToolType) {
+    return declare([ ActivationController ], {
+        _stack : null,
+        _tools : null,
+
+        constructor : function() {
+            this._stack = new Array();
+            this._tools = new Array();
+        },
+
+        activate : function(tool) {
+            safe.argumentNotNull(tool, "tool");
+
+            var me = this, current = me.getCurrent();
+
+            return this.inherited(arguments).then(function(success) {
+                if (success) {
+                    if (tool.toolType == ToolType.Oneshot && current)
+                        me._stack.push(current);
+                    else
+                        me._stack = [];
+                }
+                return success;
+            });
+        },
+
+        deactivate : function() {
+            var me = this;
+            return me.inherited(arguments).then(function(success) {
+                if (success && me._stack.length)
+                    return me.activate(me._stack.pop());
+
+                return success;
+            });
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ToolType.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,6 @@
+define([], function(){
+    return {
+        Activatable : 1,
+        Oneshot : 2
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/VectorLayer.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,68 @@
+define([
+    "implab/guard",
+    "implab/text/template-compile",
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/Deferred",
+    "ol"],
+    function (guard, compile, declare, lang, Deferred, ol) {
+        return declare([ol.layer.Vector], {
+
+            map: null,
+
+            _olMap: null,
+
+            name: null,
+
+            displayName: null,
+
+            identifyResultTemplate: null,
+
+            identifyCompiledTemplate:null,
+
+            searchResultTemplate: null,
+
+            searchCompiledTemplate: null,
+
+            constructor: function () {
+                lang.mixin(this, arguments[0]);
+                this._olMap = this.map.olMap;
+                var identifyCompiledTemplate = null, searchCompiledTemplate = null;
+                if (this.identifyResultTemplate) {
+                    this.identifyCompiledTemplate = compile(this.identifyResultTemplate);
+                }
+                if (this.searchResultTemplate) {
+                    this.searchCompiledTemplate = compile(this.searchResultTemplate);
+                }
+            },
+
+            /** Возвращает массив строк, каждая строка - результат поиска  приведенный к шаблонному виду
+                @options {Object}
+                    @str {String} поисковая строка
+                    @bbox {Object} bound box, в рамках которого осуществлять поиск
+            */
+            getSearchResult: function (options) {
+                return null;
+            },
+
+            _getIdentifyResult: function (options) {
+                var me = this;
+                var features = [];
+                //TODO: добавить фильтр по layer равный ему самому
+                this._olMap.forEachFeatureAtPixel(options.pixel, function (feature, layer) {
+                    features.push({ feature: feature, layer: layer });
+                }, null, function (layer) {
+                    return layer == me;
+                });
+                console.log(features);
+                return features;
+            },
+            /** Возвращает массив строк, каждая строка - результат идентификации приведенный к шаблонному виду
+                @options {Object}
+                    @coordinates {Array} массив описывающий координаты точки идентификации                    
+            */
+            getIdentifyResult: function (coordinates) {
+                return guard(this, "_getIdentifyResult", [coordinates]);
+            }
+        });
+    })
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/VectorStore.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,281 @@
+define(
+    [
+        "dojo/_base/declare",
+        "dojo/_base/array",
+        "implab/safe",
+        "implab/Uuid",
+        "ol",
+        "ol3/listen",
+        "./VectorStoreQuery",
+        "dojo/store/util/QueryResults"
+    ],
+    function (declare, array, safe, UUID, ol, listen, VectorStoreQuery, QueryResults) {
+        function createPaginator(opts) {
+            return (opts.count || opts.start) &&
+                function (results) {
+                    var total = results.length;
+                    results = results.slice(opts.start || 0, (opts.start || 0) +
+                        (opts.count || Infinity));
+                    results.total = total;
+
+                    return results;
+                };
+        }
+
+        /**
+         * Обертка вокруг векторного источника данных ol.source.Vector,
+         * реализует dojo/store а также notify, что делает возможным наблюдение
+         * за хранилищем при помощи dojo/store/Observable.
+         * 
+         * @disposable
+         */
+        return declare(
+            null, {
+                _source: null, // ol3.source.Vector
+
+                _subscriptions: null,
+
+                constructor: function (opts) {
+                    safe.argumentNotNull(opts, "opts");
+                    safe.argumentOfType(
+                        opts.source,
+                        ol.source.Vector,
+                        "opts.source");
+
+                    var me = this;
+
+                    me._source = opts.source;
+                },
+
+                getSource: function () {
+                    return this._source;
+                },
+
+                get: function (id) {
+                    return this._source.getFeatureById(id);
+                },
+
+                /**
+                 * @param{Object|Function} q предикат для выбора объекта
+                 * @param{Object} opts параметры выполнения (start,count,sort)
+                 * @return{Function} filter(data) filter.matches
+                 *                   filter.matches.predicate
+                 *                   filter.matches.extent filter.sort
+                 *                   filter.sort.compare
+                 */
+                queryEngine: function (q, opts) {
+                    opts = opts || {};
+
+                    // строим функцию для фильтрации
+                    var filter;
+                    if (q instanceof Function) {
+                        // если передали уже готовую функцию, испольуем ее
+                        filter = new VectorStoreQuery(q, q.extent);
+                    } else {
+                        // если передали объект
+                        var extent;
+                        // вытаскиваем из него extent
+                        if (q && 'extent' in q) {
+                            extent = q.extent;
+                            delete q.extent;
+                        }
+
+                        // строим новую функцию фильтрации
+                        filter = new VectorStoreQuery(q, extent);
+                    }
+
+                    // строим функцию сортировки
+                    var sort = opts.sort && this.sortEngine(opts.sort);
+
+                    var paginate = createPaginator(opts);
+
+                    // строим функцию выполнения запроса
+                    var execute = function (data) {
+                        var results = array.filter(data, filter);
+
+                        if (sort)
+                            sort(results);
+
+                        if (paginate)
+                            results = paginate(results);
+                        return results;
+                    };
+
+                    execute.matches = filter;
+                    execute.sort = sort;
+                    execute.paginate = paginate;
+                    return execute;
+                },
+
+                sortEngine: function (options) {
+                    var cmp = function (a, b) {
+                        for (var sort, i = 0;
+                            (sort = options[i]); i++) {
+                            var aValue = a.get(sort.attribute);
+                            var bValue = b.get(sort.attribute);
+                            if (aValue != bValue) {
+                                return Boolean(sort.descending) == aValue > bValue ? -1 : 1;
+                            }
+                        }
+                        return 0;
+                    };
+
+                    var execute = function (data) {
+                        return data.sort(cmp);
+                    };
+
+                    execute.compare = cmp;
+                    return execute;
+                },
+
+                /**
+                 * Запрашивает объекты со слоя
+                 * 
+                 * @param{object|VectorStoreQuery|Function} query
+                 * 
+                 * <pre>
+                 * {
+                 *     extent : ol3.Extent,
+                 * }
+                 * </pre>
+                 */
+                query: function (q, options) {
+                    var me = this;
+                    var filter = this.queryEngine(q, options);
+
+                    if (this.notify && !this.hasOwnProperty("_subscriptions")) {
+                        me._subscriptions = [];
+
+                        var sc = function (evt, handler) {
+                            me._subscriptions.push(listen(me._source, evt, safe.delegate(me, handler)));
+                        }
+
+                        sc("addfeature", "_onAdd");
+                        sc("changefeature", "_onUpdate");
+                        sc("removefeature", "_onRemove");
+                    }
+
+                    var predicate, data, extent = filter.matches &&
+                        filter.matches.extent;
+                    // если это запрос с указанием области
+                    if (extent) {
+                        predicate = filter.matches.predicate;
+
+                        data = this._source.getFeaturesInExtent(extent);
+
+                        if (predicate)
+                            data = array.filter(data, predicate);
+
+                        if (filter.sort)
+                            filter.sort(data);
+
+                        if (filter.paginate)
+                            data = filter.paginate(data);
+                    } else {
+                        // любой другой запрос
+                        data = filter(this._source.getFeatures());
+                    }
+
+                    return new QueryResults(data);
+                },
+
+                put: function (obj, options) {
+                    safe.argumentOfType(obj, ol.Feature, "obj");
+                    if (!options)
+                        options = {};
+
+                    if (options.id)
+                        obj.setId(options.id);
+
+                    var id = obj.getId() || new UUID();
+
+                    var prev = this.get(id);
+
+                    if ('overwrite' in options) {
+                        // overwrite=true указан, но перезаписывать нечего
+                        if (!prev && options.overwrite)
+                            throw new Error("The specified feature with id '" +
+                                id + "' doesn't exist in the store");
+
+                        // overwrite=false указан, но объект уже существует
+                        if (prev && !options.overwrite)
+                            throw new Error("The specified feature with id '" +
+                                id + "' already exists in the store");
+                    }
+
+                    // ok
+                    if (prev) {
+                        var data = obj.getProperties();
+                        prev.setProperties(data);
+                    } else {
+                        this._source.addFeature(obj);
+                    }
+
+                    return id;
+                },
+
+                add: function (obj, options) {
+                    safe.argumentOfType(obj, ol.Feature, "obj");
+
+                    if (!options)
+                        options = {};
+
+                    if (options.id)
+                        obj.setId(options.id);
+
+                    var id = obj.getId() || new UUID();
+
+                    var prev = this.get(id);
+
+                    if (prev)
+                        throw new Error("The specified feature with id '" + id +
+                            "' already exists in the store");
+
+                    this._source.addFeature(obj);
+                },
+
+                remove: function (id) {
+                    var me = this;
+
+                    var ft = me.get(id);
+                    if (ft)
+                        me._source.removeFeature(ft);
+                },
+
+                getIdentity: function (obj) {
+                    if (safe.isNull(obj))
+                        return undefined;
+                    if (!(obj instanceof ol.Feature))
+                        throw new Error("A feature is expected");
+
+                    return obj.getId();
+                },
+
+                _onAdd: function (ev) {
+                    this.notify(ev.feature);
+                },
+
+                _onRemove: function (ev) {
+                    var id = ev.feature.getId();
+                    if (!safe.isNull(id))
+                        this.notify(undefined, id);
+                },
+
+                _onUpdate: function (ev) {
+                    var id = ev.feature.getId();
+                    if (!safe.isNull(id))
+                        this.notify(ev.feature, id);
+                },
+
+                dispose: function () {
+                    var me = this;
+                    if (me._subscriptions)
+                        me._subscriptions.forEach(function (sc) {
+                            sc.remove();
+                        });
+
+                    me._source = null;
+                    me._subscriptions = null;
+                }
+            });
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/VectorStoreQuery.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,67 @@
+define([ "ol" ], function(ol) {
+
+    function buildFilter(filter) {
+        if (filter instanceof Function)
+            return filter;
+        if (filter) {
+            var match = function(str) {
+                return {
+                    test : function(x) {
+                        if (x === null || x === undefined)
+                            return false;
+                        return x.toString().startsWith(str);
+                    }
+                };
+            };
+
+            for ( var p in filter) {
+                if (typeof (filter[p]) == "string" && filter[p].endsWith("*"))
+                    filter[p] = match(filter[p].substr(0, filter[p].length));
+            }
+            return function(ft) {
+                for ( var p in filter) {
+                    if (filter[p] && filter[p].test ? !filter[p]
+                        .test(ft.get(p)) : ft.get(p) != filter[p])
+                        return false;
+                }
+                return true;
+            };
+        }
+        throw new Error("Unsupported filter");
+    }
+
+    /**
+     * @constructor
+     * @example
+     * 
+     * <pre>
+     * var store = new VectorStore({
+     *     source : vectorSource
+     * });
+     * 
+     * var req = new VectorStoreQuery({
+     *     city : &quot;Moscow&quot;
+     * }, [ 30, 50, 40, 60 ]);
+     * 
+     * store.query(req).then(showResults);
+     * </pre>
+     */
+    return function(filter, extent) {
+        var match = filter && buildFilter(filter);
+
+        var query = function(ft) {
+            if (extent) {
+                var g = gt.getGeometry();
+                if (!g || !ol.extent.intersects(extent, g.getExtent()))
+                    return false;
+            }
+
+            return !match || match(ft);
+        };
+
+        query.extent = extent;
+        query.predicate = match;
+
+        return query;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/WFSSource.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,112 @@
+define([ "ol", "dojo/request", "dojo/_base/array", "implab/safe"],
+
+function(ol, request, array, safe) {
+    return function(wfs, featurePrefix, featureType, featureNS, queryArgs) {
+        if (arguments.length == 1) {
+            featurePrefix = wfs.featurePrefix;
+            featureType = wfs.featureType;
+            featureNS = wfs.featureNS;
+            wfs = wfs.wfsURL;
+            queryArgs = wfs.queryArgs;
+        }
+
+        safe.argumentNotNull(wfs, "wfsURL");
+        safe.argumentNotEmpty(featurePrefix, "featurePrefix");
+        safe.argumentNotEmpty(featureNS, "featureNS");
+        safe.argumentNotEmpty(featureType, "featureType");
+
+        var format = new ol.format.WFS({
+            featureNS : featureNS,
+            featureType : featureType
+        });
+
+        var layerName = featurePrefix + ":" + featureType;
+
+        function loader(extent, resolution, projection) {
+            var query = {
+                service : 'WFS',
+                version : '1.1.0',
+                request : 'GetFeature',
+                typename : layerName,
+                srsname : projection.getCode()
+            };
+            safe.mixin(query, queryArgs);
+            
+            if (extent && isFinite(extent[0]))
+                query.bbox = extent.join(',') + "," + projection.getCode();
+            
+            return request(wfs, {
+                query : query,
+                handleAs : 'xml'
+            }).then(function(data) {
+                // в загрузчике нельзя вызывать метод source.clear() поскольку
+                // это приводит к рекурсии
+                var features = format.readFeatures(data);
+
+                var map = {}, del = [], add = [];
+
+                array.forEach(features, function(x) {
+                    // HACK исправляем идентификаторы, чтобы они совпадали с
+                    // реальными
+
+                    var id = x.get("id");
+                    if (id)
+                        x.setId(id);
+                    else
+                        id = x.getId();
+
+                    map[id] = x;
+
+                    // нужно проверить, была ли фича на карте
+                    var prev = source.getFeatureById(id);
+                    if (prev) {
+                        // если да, то обновить ее.
+                        var data = x.getProperties();
+                        prev.setProperties(data);
+                    } else {
+                        // иначе добавить
+                        add.push(x);
+                    }
+                });
+
+                source.forEachFeatureInExtent(extent, function(x) {
+                    if (!(x.getId() in map))
+                        del.push(x);
+                });
+
+                source.addFeatures(add);
+
+                array.forEach(del, function(x) {
+                    source.removeFeature(x);
+                });
+
+                //revision = revision + 1;
+
+                source.set("revision", ++revision);
+
+            });
+        }
+
+        var cls = ol.source.ServerVector || ol.source.Vector;
+        var revision = 0;
+        var source = new cls({
+            loader : loader,
+            //revision: revision
+            wrapX : false
+        // ,
+        // strategy : options.strategy || ol.loadingstrategy.all,
+        // projection : options.projection
+        });
+        source.set("revision", revision);
+        source.reload = function(extent,resolution, projection, q) {
+            if (arguments.length >= 4)
+                queryArgs = q;
+            if (!extent)
+                extent = [-Infinity, -Infinity, Infinity, Infinity];
+            return loader(extent,resolution,projection);
+        };
+        
+        console.log(wfs, layerName);
+        return source;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/_OneshotTool.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,65 @@
+define([ "dojo/_base/declare", "implab/safe", "implab/guard", "ol3/_ToolBase", "ol3/ToolType" ],
+
+function(declare, safe, guard, _ToolBase, ToolType) {
+    return declare([ _ToolBase ], {
+        _pending : null,
+        
+        _lastResult : null,
+
+        toolType : ToolType.Oneshot,
+
+        invoke : function() {
+        },
+
+        onActivating : function() {
+            var me = this;
+            
+            // start the operation
+            me._lastResult = me._pending = guard(me,"invoke");
+            
+            return this.inherited(arguments);
+        },
+        
+        onActivated : function() {
+            var me = this;
+            
+            // fire the activate event
+            this.inherited(arguments);
+            
+            me._pending.then(function() {
+                if (me._pending) {
+                    me.log("Operation finished, deactivating.");
+                    me._pending = null;
+                    me.deactivate();
+                }
+            }, function(ex) {
+                if (me._pending) {
+                    me.error("Operation failed, deactivating: {0}", ex);
+                    me._pending = null;
+                    me.deactivate();
+                }
+            });
+        },
+
+        onDeactivated : function() {
+            var d = this._pending;
+            if (d) {
+                this.log("Cancelling pending operation");
+                this._pending = null;
+                d.cancel();
+            }
+
+            return this.inherited(arguments);
+        },
+        
+        run : function() {
+            var me = this;
+            
+            return me.activate().then(function(success) {
+                if (success)
+                    return me._lastResult;
+                throw new Error("Operation declined");
+            });
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/_ToolBase.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,46 @@
+define([
+    "dojo/_base/declare",
+    "dojo/when",
+    "implab/safe",
+    "implab/guard",
+    "implab/components/_ActivatableMixin",
+    "implab/log/_LogMixin",
+    "dojo/Evented",
+    "./ToolType" ],
+
+function(declare, when, safe, guard, _ActivatableMixin, _LogMixin, Evented, ToolType) {
+    return declare([ _ActivatableMixin,  Evented ], {
+        toolType : ToolType.Activatable,
+        
+        module : null,
+
+        constructor : function(opts) {
+            if (opts) {
+                if (opts.controller)
+                    this.setController(opts.controller);
+            }
+        },
+        
+        onActivating : function() {
+            var me = this, inherited = this.getInherited(arguments);
+            if (me.module && !me.module.isActive())
+                return me.module.activate().then(function(active) {
+                    return active ? inherited.apply(me) : false;
+                });
+            else
+                return inherited.apply(me);
+        },
+
+        onActivated : function() {
+            this.emit("active", true);
+        },
+
+        onDeactivated : function() {
+            this.emit("active", false);
+        },
+        
+        destroy : function() {
+            
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/declare-style.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,20 @@
+define(["dojo/_base/declare","implab/safe"],function(declare, safe){
+    return function(base, proto){
+        var cls = declare(base,proto);
+        
+        var factory = function() {
+            var me = this;
+            cls.apply(me,arguments);
+            var fn = function() {
+                return me.getFeatureStyle.apply(me,arguments);
+            };
+            fn.style = me;
+            fn.styleFunction = fn;
+            return fn;
+        };
+        
+        factory.styleClass = cls; 
+        factory.prototype = cls.prototype;
+        return factory;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/format/coords.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,71 @@
+define(
+    [ "dojo/i18n!./nls/coords", "implab/text/format", "implab/safe" ],
+
+    function(nls, format, safe) {
+
+        var formatDMS = function(coord) {
+            return formatSD(coord, nls.dmsPattern);
+        };
+
+        var formatDM = function(coord) {
+            return formatSD(coord, nls.dmPattern);
+        };
+
+        var formatD = function(coord) {
+            return formatSD(coord, nls.dPattern);
+        };
+
+        /**
+         * pattern:
+         * 
+         * {0} - signed floating point number - latitude
+         * 
+         * {1} - positive floating point number - minutes part of latitude
+         * 
+         * {2} - positive floating point number - seconds part of latitude
+         * 
+         * {3} - localized hemisphere sign: north or south
+         * 
+         * {4} - signed floating point number - longitude
+         * 
+         * {5} - positive floating point number - minutes part of longitude
+         * 
+         * {6} - positive floating point number - seconds part of longitude
+         * 
+         * {7} - localized hemisphere sign: east or west
+         */
+        var formatSD = function(coord, pattern) {
+            safe.argumentNotNull(coord, "coord");
+            if (!pattern)
+                pattern = nls.sdPattern;
+            var x = (coord[0] % 360 + 540) % 360 - 180, y = (coord[1] % 180 + 270) % 180 - 90;
+
+            return format(pattern, y, Math.abs((y * 60) % 60), Math
+                .abs((y * 3600) % 60), y >= 0 ? nls.north : nls.south, x, Math
+                .abs((x * 60) % 60), Math.abs((x * 3600) % 60), x >= 0
+                ? nls.east
+                : nls.west);
+        };
+
+        var cls = function(fmt) {
+            switch (fmt) {
+            case "DMS":
+                return formatDMS;
+            case "DM":
+                return formatDM;
+            case "D":
+                return formatD;
+            case "SD":
+                return formatSD;
+            default:
+                if (!fmt)
+                    return formatSD;
+                else
+                    return function(coord) {
+                        return formatSD(coord, fmt);
+                    }
+            }
+        };
+
+        return cls;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/format/nls/coords.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,14 @@
+define({
+    root : {
+        // lat lon
+        dmsPattern : "{0:!00;00}\u00b0{1:!00}\u2032{2:00}\u2033{3} {4:!000;000}\u00b0{5:!00}\u2032{6:00}\u2033{7}",
+        dmPattern : "{0:!00;00}\u00b0{1:00.0000}\u2032{3} {4:000;000}\u00b0{5:!00.0000}\u2032{7}",
+        dPattern : "{0:00.000000;00.000000}\u00b0{3} {4:000.000000;000.000000}\u00b0{7}",
+        sdPattern : "{0:#0.000000}, {4:#0.000000}",
+        north : "N",
+        south : "S",
+        west : "W",
+        east : "E"
+    },
+    ru : true
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/format/nls/ru/coords.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,6 @@
+define({
+    north : "С",
+    south : "Ю",
+    west : "З",
+    east : "В"
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/format/nls/ru/units.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,11 @@
+define({
+    kmph : "км/ч",
+    mps : "м/с",
+    knots : "уз",
+    meters : "м",
+    kilometers : "км",
+    meters2 : "м<sup>2</sup>",
+    kilometers2 : "км<sup>2</sup>",
+    nmiles : "миль",
+    nmiles2 : "миль<sup>2</sup>"
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/format/nls/units.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,14 @@
+define({
+    root : {
+        kmph: "km/h",
+        mps: "m/s",
+        knots : "kn",
+        meters : "m",
+        kilometers : "km",
+        meters2 : "m<sup>2</sup>",
+        kilometers2 : "m<sup>2</sup>",
+        nmiles : "nmi",
+        nmiles2 : "nmi<sup>2</sup>"
+    },
+    ru : true
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/interaction/FeatureDrag.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,123 @@
+define([ "dojo/_base/declare", "ol", "dojo/Evented" ], function(declare, ol,
+    Evented) {
+
+    var peekFirstFeature = function(map, coordinate, pixel) {
+        return map.forEachFeatureAtPixel(function(ft, layer) {
+            return ft;
+        });
+    };
+
+    var cls = declare([ ol.interaction.Pointer ], {
+        "-chains-" : {
+            constructor : "manual"
+        },
+
+        _peek : null,
+
+        _feature : null,
+
+        _coordinate : null,
+
+        cursor : "pointer",
+
+        _oldCursor : undefined,
+
+        /**
+         * Создает новый объект
+         * 
+         * @param {Object}
+         *            opts опции
+         * @param {Function}
+         *            opts.peek Функция выбора фичи для перетаскивания
+         *            function(map,coordinate,pixel), возвращает фичу
+         * @param {String}
+         *            opts.cursor css курсор который будет использован при
+         *            наведении и перетаскивании фичи
+         * 
+         */
+        constructor : function(opts) {
+            
+            ol.interaction.Pointer.apply(this, [{
+                handleDownEvent : this.handleDownEvent,
+                handleDragEvent : this.handleDragEvent,
+                handleMoveEvent : this.handleMoveEvent,
+                handleUpEvent : this.handleUpEvent
+            }]);
+            Evented.apply(this);
+
+            if (opts) {
+                if (opts.peek) {
+                    this._peek = opts.peek;
+                } else {
+                    this._peek = peekFirstFeature;
+                }
+                if ("cursor" in opts)
+                    this.cursor = opts.cursor;
+            }
+        },
+
+        handleDownEvent : function(evt) {
+            var c = evt.coordinate;
+
+            var ft = this._peek(evt.map, c, evt.pixel);
+
+            if (ft) {
+                this._feature = ft;
+                this._coordinate = c;
+                this._emit("dragbegin", { feature : ft });
+                return true;
+            }
+
+            return false;
+        },
+
+        handleDragEvent : function(evt) {
+            var c1 = this._coordinate, c2 = evt.coordinate;
+
+            var dx = c2[0] - c1[0];
+            var dy = c2[1] - c1[1];
+
+            this._emit("dragging", { feature : this._feature, fromCoord : c1, toCoord : c2 });
+
+            this._feature.getGeometry().translate(dx, dy);
+
+            this._coordinate = c2;
+        },
+
+        handleUpEvent : function(evt) {
+            if (this._feature)
+                this._emit("dragend", { feature : this._feature });
+
+            this._feature = null;
+            this._coordinate = null;
+            return false;
+        },
+
+        handleMoveEvent : function(evt) {
+            if (this.cursor) {
+                var ft = this._feature || this._peek(evt.map, evt.coordinate , evt.pixel);
+
+                var element = evt.map.getTargetElement();
+                if (ft) {
+                    if (element.style.cursor != this.cursor) {
+                        this._oldCursor = element.style.cursor;
+                        element.style.cursor = this.cursor;
+                    }
+                } else if (this._oldCursor !== undefined) {
+                    element.style.cursor = this._oldCursor;
+                    this._oldCursor = undefined;
+                }
+            }
+        },
+        
+        _emit : function(name, data) {
+            var evt = new ol.source.VectorEvent(name,this);
+            
+            for(var p in data)
+                evt[p] = data[p];
+            this.dispatchEvent(evt);
+        }
+    });
+    
+    return cls;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/listen.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,22 @@
+define(["ol"], function(ol) {
+
+    var listen = function(target, event, callback) {
+        var key = target.on(event, callback);
+        return {
+            remove : function() {
+                ol.Observable.unByKey(key);
+            }
+        };
+    };
+
+    listen.once =  function(target, event, callback) {
+        var key = target.once(event, callback);
+        return {
+            remove : function() {
+                ol.Observable.unByKey(key);
+            }
+        };
+    };
+
+    return listen;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/main.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,6 @@
+define([ "ol" ], function(ol) {
+    // вспомогательный класс для получения ol в виде зависимости
+    return function() {
+        return ol;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/ol-stub.js	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,4 @@
+var ol;
+define([], function() {
+    return ol;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/resources/LayerCheckBoxTemplate.html	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,4 @@
+<div class="poisk-layer-checkbox">
+	<input type="checkbox" data-dojo-type="dijit/form/CheckBox" data-dojo-attach-point="checkBox"/><span
+		class="label" data-dojo-attach-point="labelNode"></span>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/resources/LayerRadioButtonTemplate.html	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,5 @@
+<div class="poisk-layer-radiobutton">
+	<input type="radio" data-dojo-attach-point="radioButton"
+		data-dojo-type="dijit/form/RadioButton" /><span
+		data-dojo-attach-point="labelNode"></span>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/resources/LayerSwitcherTemplate.html	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,6 @@
+<div class="poisk-layer-switcher">
+	<h4 class="group-label" data-dojo-attach-point="infoLayersLabelNode"></h4>
+    <div data-dojo-attach-point="infoLayersContainer" data-dojo-type="app/view/Container"></div>
+    <h4 class="group-label" data-dojo-attach-point="baseLayersLabelNode"></h4>
+    <div data-dojo-attach-point="baseLayersContainer" data-dojo-type="app/view/Container"></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/resources/PopupContainerTemplate.html	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,8 @@
+<div class="ol-popup">
+    <div data-dojo-attach-point="popupOverlay" class="popup-overlay">
+        <span class="inline-ghost"></span>
+        <div class="fa fa-spinner fa-pulse"></div>
+    </div>
+    <div class="ol-popup-closer fa fa-times" data-dojo-attach-point="popupCloser"></div>
+    <div class="popup-content" data-dojo-attach-point="popupContent"></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/djol/resources/mapToolCheckBox.html	Mon Aug 21 18:03:00 2017 +0300
@@ -0,0 +1,3 @@
+<div class="mapToolCheckBox poisk-measure-control tooltipMatchNode below">
+    <div data-dojo-attach-point="iconsBlock" class="iconsBlock"></div>
+</div>
\ No newline at end of file
--- a/src/implab/data/ObjectStore.js	Mon Jun 26 13:27:27 2017 +0300
+++ b/src/implab/data/ObjectStore.js	Mon Aug 21 18:03:00 2017 +0300
@@ -2,10 +2,10 @@
     "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare,
     lang, array, safe, when, Deferred, QueryResults) {
     /**
-     * @module core/data/RestStore
+     * @module implab/data/RestStore
      * 
      * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и
-     * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования
+     * отправке данных в нижележащие хранилище используется implab/data/MapSchema для преобразования
      * данных.
      */
     return declare(null, {
--- a/src/implab/data/RestStore.js	Mon Jun 26 13:27:27 2017 +0300
+++ b/src/implab/data/RestStore.js	Mon Aug 21 18:03:00 2017 +0300
@@ -2,10 +2,10 @@
     "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare,
     lang, array, safe, when, Deferred, QueryResults) {
     /**
-     * @module core/data/RestStore
+     * @module implab/data/RestStore
      * 
      * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и
-     * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования
+     * отправке данных в нижележащие хранилище используется implab/data/MapSchema для преобразования
      * данных.
      */
     return declare(null, {
--- a/src/implab/safe.js	Mon Jun 26 13:27:27 2017 +0300
+++ b/src/implab/safe.js	Mon Aug 21 18:03:00 2017 +0300
@@ -143,7 +143,7 @@
              * @param{Function|String} fn [Required] Function wich will be wrapped.
              */
             async: function (fn, thisArg) {
-                if (arguments.length == 2)
+                if (arguments.length == 2 && !(fn instanceof Function))
                     fn = thisArg[fn];
 
                 if (fn == null)
--- a/src/implab/text/template-compile.js	Mon Jun 26 13:27:27 2017 +0300
+++ b/src/implab/text/template-compile.js	Mon Aug 21 18:03:00 2017 +0300
@@ -44,7 +44,7 @@
                 }, function(err) {
                     require.signal("error", [ {
                         error : err,
-                        src : 'implab/template-compile'
+                        src : 'implab/text/template-compile'
                     } ]);
                 });
             }