В данной статье будет рассмотрено создание модуля «районы» для mapSurfer.
Выполнение модуля начинается с создания экземпляра класса GP.Controller.DistrictsController, который наследуется от GP.Controller.
GP.Module.Controller.Districts = GP.Controller.extend({...});
В котором описываются пути относительно основного проекта, по которым будут обрабатываться http запросы и лежать картинки:
statics: {
BASE_PATH: "/modules/districts/", //путь для запросов
IMAGE_PATH: "/public/modules/districts/images/" путь для картинок
},
Далее описывается конструктор контроллера:
initialize: function() {
GP.Controller.prototype.initialize.call(this,{}); //вызов конструктора родителя,
//обязательно для контроллеров
this._loadDistricts(); //действия модуля
},
В контроллере GP.Controller.DistrictsController GET запросом на url ‘/modules/districts/districtsList’ получаем список районов в формате json,
_loadDistricts: function(){
$.ajax({
type: 'GET', //тип запроса
url: GP.Module.Controller.Districts.BASE_PATH+'list', //url, на который отправляется запрос
success: M.Util.bind(this._saveToStore,this), //функция, выполняемая при
// успешном выполнении запроса
error: function(data){ //функция, выполняемая при возникновении
//ошибки обработки запроса
}
});
},
Для хранения данных удобно использовать Store.
В первую очередь нужно создать модель, в которой необходимо описать все составляющие элементов стора.
GP.Model.DistrictsModel = GP.Model.extend({
fields: [
{
name: "id", //имя поля
type: "integer", //тип значений поля
dvalue: 0, //значение по умолчанию
notNull: true, //может ли поле быть undefined, если false то да
rusName: "Id" //русское название
},
{name: "title", type: "string", dvalue: "", notNull:true, rusName: "title"},
{name: "subTitle", type: "string", dvalue: "", notNull:true, rusName: "subTitle"},
{name: "geom", type: "object", dvalue: "", notNull:true, rusName: "geom"},
{name: "districtFields", type: "object", dvalue: [], notNull:true, rusName: "fields"},
{name: "bbox", type: "object",dvalue: "", notNull:true,rusName:"bBox"}
]
});
Следующим шагом нужно описать стор.
GP.Store.DistrictsStore = GP.Store.extend({
model: GP.Model.DistrictsModel
});
Затем, на основе полученных данных создается GP.stores.districtsStore (экземпляр класса GP.Store.DistrictsStore, GP.Store.DistrictsStore – класс хранилища данных, удовлетворяющих модели, описанной в GP.Model.DistrictsModel):
_saveToStore: function(data){
GP.stores.districtsStore = new GP.Module.Store.Districts(data.districts);
/* в переменной data.districts хранится массив,
каждый элемент которого удовлетворяет моделе GP.Model.DistrictsModel*/
...
}
Следующим шагом добавляем вкладку с районами в правую панель:
GP.widgets.tabs.addTab({
name : "Районы", //надпись, выводимая при наведении
image : GP.Module.Controller.Districts.IMAGE_PATH + "/icons/district.png", //иконка, отображаемая,
// когда вкладка неактивна
imageWhite : GP.Module.Controller.Districts.IMAGE_PATH + "/icons/district-white.png", //иконка,
// отображаемая, когда вкладка активна
divId : "districtsList-block"}); //id, присваиваемый элементу вкладки
Далее происходит создание виджета, ответственного за прорисовку внутренности вкладки
GP.widgets.districtsList = new GP.Widget.DistrictsList({mainDivId:"districtsList-block"});
Функция _resize класса GP.Widget.DistrictsList выполняется при изменении размеров правой панели, то есть при срабатывании события «tab:resze».
GP.widgets.tabs.on("tab:resize",this._resize,this);//выполнять функцию this._resize
// при срабатывании события "tab:resze"
_resize: function(){
var cHeight = this._mainElement.height() - 6; //вычисление высоты элемента
//относительно родительского элемента, высота которого уже изменилась
this._blockElement.css("height",cHeight); //установка высоты элементу
setTimeout(M.Util.bind(function(){
this._districtsElement.mCustomScrollbar("update"); //обновление полосы прокрутки
},this), 300);
}
При нажатии на выбранный район создается левая панель:
self._districtInfo = new GP.Widget.DistrictsLeftPanel({
districtId: data.data.id}, //id выбранного района
"#wrap") //id элемента, в который поместить левую панель
Если же панель уже создана, то просто меняется содержимое:
self._districtInfo.setDistrict(data.data.id).
Класс GP.Widget.DistrictsLeftPanel наследуется от GP.Widget.LeftPanel. После создания классом GP.Widget.LeftPanel левой панели будет выполняться функция _createContent, которую мы в своем классе GP.Widget.DistrictsLeftPanel переопределяем для отрисовки нужной нам информации.
Для создания и отображения слоя с районом на карте используется функция:
_drawLayer: function(){
var geom = GP.stores.districtsStore.getById(this._districtId).get("geom");//достаем геометрию из store
/* переменная geom - строка, содержащая в себе данные в формате GeoJSON */
var object = jQuery.parseJSON(geom); //создаем json объект из строки
this._layer = new M.GeoJSON(object); //создаем слой из геометрии
GP.mainMap.map.addLayer(this._layer); //добавляем слой на карту
var bbox = GP.stores.districtsStore.getById(this._districtId).get("bbox");
GP.mainMap.setBBox(bbox.minX,bbox.minY,bbox.maxX,bbox.maxY,bbox.projection); //переходим
//к выбранному району на карте, устанавливая карте bBox
}
Для того, чтобы убрать слой с карты необходимо выполнить следующую функцию:
GP.mainMap.map.removeLayer(this._layer) // this._layer – слой, который нужно убрать.
Серверная часть
Файл geoportal.modules.districts.routes. В этом файле содержатся соответствия адесов для http запросов и контроллеров, которые будут обрабатывать запросы.
тип запросапуть относительно корня модуля, по которому будут обрабатываться запросы. Итоговый путь относительно всего сайта будет выглядеть следующим образом: /modules/districts/listфункция, обрабатывающая запросы по данному пути.
GET | /list | controllers.geoportal.modules.districts. Application.getListDistricts |
База данных.
В базе данных находятся 3 таблицы, которые нам предстоит использовать:
1) «districts_config», находящаяся в схеме «districts». За работу с этой таблицей отвечает класс модель, который описывается следующим образом:
@Entity //аннотация Ebean, означающая ,что класс будет взаимодействовать с БД
@Table(schema = "districts", name = "districts_config") //аннотация Ebean,
// указывающая схему и таблицу в БД, с которой класс будет сопоставляться
public class DistrictsConfig {
...
}
2) «districts_fields», находящаяся в схеме «districts». За работу с этой таблицей отвечает класс модель, который описывается следующим образом:
import play.db.ebean.Model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(schema = "districts", name = "districts_fields")
public class DistrictsFields {
@Id //аннотация Ebean, означающая, что переменная будет являться primary key
private Long id;
...
...
/* переменная find нужна для писка данных из таблицы, соответствующей данному классу в БД */
public static Model.Finder<Long,DistrictsFields> find =
new Model.Finder(Long.class, DistrictsFields.class);
}
3) таблица, название которой заранее неизвестно, за взаимодействие с этой таблицей отвечает класс Districts.java — класс — модель, создаваемый на основе sql запроса
@Entity
@Sql //аннотация Ebean, означающая, что экземпляр класса будет создаваться
// на основе sql запроса
public class District extends Model {
@Id
private Long id;
private Geometry geom; //связь com.vividsolutions.jts.geom.Geometry и геометрией в бд
private List districtFields;
/* при парсинге экземпляра класса в json, список полей района будет сортироваться */
public List getDistrictFields() {
DistrictField.sort(districtFields);
return districtFields;
}
/* при парсинге экземпляра класса в json, поле с геометрией будет парситься
с помощью org.geotools.geojson.geom.GeometryJSON */
public String getGeom() {
GeometryJSON geometryJSON = new GeometryJSON();
String geomJson = geometryJSON.toString(geom);
return geomJson;
}
public static List findDistrict(DistrictsConfig districtConfig) {
/* подготовка данных для запроса */
String title = StringUtils.buildString(districtConfig.getTitleColumn(), " as title ");
String subTitle = StringUtils.buildString(districtConfig.getSubtitleColumn(), " as subTitle ");
String idColumn = StringUtils.buildString(districtConfig.getIdColumn());
String geom = StringUtils.buildString(districtConfig.getGeomColumn(), " as geom");
String tables = StringUtils.buildString(districtConfig.getInfoTable());
String orderBy = districtConfig.getOrderBy();
/* поиск всех записей в таблице districts_fields
(название и расположение полей с информацией о районе в основной таблице) */
List districtsFieldsList = DistrictsFields.find.all();
/* подготовка sql запроса */
String sqlSelect = StringUtils.buildString("SELECT ", title, ", ", subTitle, ", ", idColumn, " as id , ", geom,
" FROM ", tables, " ORDER BY ", orderBy);
/* создание экземпляра класса District на основе sql запроса */
RawSQLFinder find = new RawSQLFinder(District.class, sqlSelect, null);
Query finder = find;
List districtList = finder.findList();
/* формирование полей с дополнительной информацией о районе
(напр. насиление, глава района и т.д) */
for (District district : districtList) {
district.setDistrictFields(new ArrayList());
String id = district.getId().toString();
String fieldColumns = "";
for (DistrictsFields districtsFields : districtsFieldsList) {
if (fieldColumns.equals(""))
fieldColumns = districtsFields.getFieldColumn();
else {
fieldColumns = StringUtils.buildString(fieldColumns, ",", districtsFields.getFieldColumn());
}
}
String sqlFieldSelect = StringUtils.buildString("SELECT ", fieldColumns, " FROM ", tables, " WHERE ",
idColumn, "=", id);
List sqlFieldRows = Ebean.createSqlQuery(sqlFieldSelect).findList();
for (SqlRow sqlFieldRow : sqlFieldRows) {
for (DistrictsFields districtsFields : districtsFieldsList) {
district.districtFieldsAdd(new DistrictField(districtsFields.getFieldLabel(),
sqlFieldRow.getString(districtsFields.getFieldColumn()),
districtsFields.getOrder(),districtsFields.getHideIfNull()));
}
}
}
return districtList;
}
}
Контроллер Application.java.
Класс Application.java наследуется от Abstract, так как в этом абстрактном классе содержатся вспомогательные функции, которые нам пригодятся.
public class Application extends Abstract {
public static Result getListDistricts() {
try{
List districtsConfigList = DistrictsConfig.find.all(); //поиск всех элементов в таблице
// districts_config
List items = new ArrayList();
for (DistrictsConfig districtsConfig : districtsConfigList) {
items = District.findDistrict(districtsConfig); //составление списка районов
}
ObjectNode result = Json.newObject();
result.put("districts", Json.toJson(items)); //парсинг данных в json
return crossdomain( //функция crossdomain реализована в классе Abstract
ok(result).as("Content-Type: application/json; charset=UTF-8")); //возвращение клиенту
//подготовленный json объект с кодом 200
}catch (Exception e){
return crossdomain(internalServerError(e.getMessage()).
as("Content-Type: application/json; charset=UTF-8")); //возвращение ошибку в случае ее возникновения
}
}
}