пятница, 15 апреля 2011 г.

Запрос типа wfs:dwithin к серверу GeoServer из GWT-OpenLayers без использования gwtOpenlayersProxy

Разрабатывается веб-приложение на основе SmartGWT с поддержкой картографии. Приложение работает под Apache Tomcat. Картографические данные хранятся в PostgreSQL с использованием PostGIS. Эти данные через GeoServer в формате WMS приложение получает посредством GWT-OpenLayers.

Существенно, что GeoServer, PostgreSQL и разрабатываемое приложение располагаются на разных серверах. Потребовалось по клику на точечном объекте получить его данные из GeoServer (имя слоя и идентификатор выбранного объекта для последующего запроса к PosgreSQL).

Известно, что OpenLayers не получает ответа на запрос типа OpenLayers.loadURL, если он загружен с другого хоста. Для преодоления этого препятствия разработчиками OpenLayers было предложено использовать прокси. В GWT-OpenLayers реализован такой прокси. Это сервлет GwtOpenLayersProxyServlet, который размещён в gwt-openlayers-server-0.5.jar.

В процессе разработки выяснилось, что этот способ не работает! Помогла статья wfs:dwithin MassGIS Geospatial Web Services Wiki. В ней предложено два способа запроса к WFS GeoServer: через HTTP POST XML и через HTTP GET с параметром cql_filter.

Выполняя запрос
http://webgar:8080/geoserver/wfs?request=getfeature
&version=1.1.0&service=wfs&outputformat=json&typename=rptp:ps
&propertyName=tip,nomer_tp&cql_filter=DWITHIN(geom,POINT(15398 14449),600,meters)
я получаю нормальный ответ
{"type":"FeatureCollection","features":
[{"type":"Feature","id":"ps.9","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Ново-Измайлово\""}}]}
При отправке того же запроса через прокси
http://localhost:8080/map_war/gwtOpenLayersProxy?targetURL=
http%3A%2F%2Fwebgar%3A8080%2Fgeoserver%2Fwfs%3Frequest%3Dgetfeature
%26version%3D1.1.0%26service%3Dwfs%26outputformat%3Djson
%26typename%3Drptp%3Aps%26propertyName%3Dtip%2Cnomer_tp
%26cql_filter%3DDWITHIN(geom%2CPOINT(15398%2014449)%2C600%2Cmeters)
возвращается ошибка
HTTP Status 500 -
type Status report
description The server encountered an internal error () that prevented it from fulfilling this request.
Apache Tomcat/7.0.10
Я попробовал убрать параметр cql_filter:
http://localhost:8080/map_war/gwtOpenLayersProxy?targetURL=
http%3A%2F%2Fwebgar%3A8080%2Fgeoserver%2Fwfs%3Frequest%3Dgetfeature%26version
%3D1.1.0%26service%3Dwfs%26outputformat%3Djson%26typename%3Drptp%3Aps
%26propertyName%3Dtip%2Cnomer_tp
В этом случае возвращается корректный ответ:
{"type":"FeatureCollection","features":
[{"type":"Feature","id":"ps.1","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"53 ПС \"Герцево\""}},
{"type":"Feature","id":"ps.2","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Матвеевская\""}},
{"type":"Feature","id":"ps.3","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Щедрино\""}},
{"type":"Feature","id":"ps.4","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"850 \"Ново-Внуково\""}},
{"type":"Feature","id":"ps.5","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Дубнинская\""}},
{"type":"Feature","id":"ps.6","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"68 ПС \"Битца\""}},
{"type":"Feature","id":"ps.7","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"844 ПС \"Магистральна"}},
{"type":"Feature","id":"ps.8","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Мневники\""}},
{"type":"Feature","id":"ps.9","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Ново-Измайлово\""}},
{"type":"Feature","id":"ps.10","geometry":null,"properties":
{"tip":"ПС","nomer_tp":"ПС \"Горьковская\""}}]}
Выяснилось, что параметр cql_filter допустим, но при использовании DWITHIN возникает ошибка. Можно послать запрос на GeoServer утилитой curl:
curl.exe -H "Content-type: text/xml" -d @request.xml
 -X POST http://webgar:8080/geoserver/wfs > response.xml
В файле request.xml содержится тег DWithin:
<wfs:GetFeature xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs
 http://webgar:8080/geoserver/schemas/wfs/1.1.0/WFS-basic.xsd"
xmlns:gml="http://www.opengis.net/gml"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc" service="WFS"
version="1.1.0">
  <wfs:Query typeName="rptp:ps"
  xmlns:rptp="http://rptp.dev-city.ru">
    <ogc:PropertyName>tip</ogc:PropertyName>
    <ogc:PropertyName>nomer_tp</ogc:PropertyName>
    <ogc:Filter>
      <ogc:DWithin>
        <ogc:PropertyName>geom</ogc:PropertyName>
        <gml:Point srsName="EPSG:100001"
        xmlns:gml="http://www.opengis.net/gml">
          <gml:coordinates decimal="." cs="," ts="">
          15398,14449</gml:coordinates>
        </gml:Point>
        <ogc:Distance units="meter">600</ogc:Distance>
      </ogc:DWithin>
    </ogc:Filter>
  </wfs:Query>
</wfs:GetFeature>
В результате получим response.xml:
<?xml version="1.0" encoding="utf-8"?>
<wfs:FeatureCollection numberOfFeatures="1"
timeStamp="2011-04-15T10:47:52.440+04:00"
xsi:schemaLocation="http://rptp.dev-city.ru 
http://webgar:8080/geoserver/wfs?service=WFS&amp;version=1.1.0&amp;
request=DescribeFeatureType&amp;typeName=rptp%3Aps http://www.opengis.net/wfs
 http://webgar:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd"
xmlns:egko="http://egko.dev-city.ru"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:rptp="http://rptp.dev-city.ru"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ows="http://www.opengis.net/ows"
xmlns:wfs="http://www.opengis.net/wfs">
  <gml:featureMembers>
    <rptp:ps gml:id="ps.9">
      <rptp:tip>ПС</rptp:tip>
      <rptp:nomer_tp>ПС "Ново-Измайлово"</rptp:nomer_tp>
    </rptp:ps>
  </gml:featureMembers>
</wfs:FeatureCollection>
Так что, поставленный эксперимент завершился удачей.
Осталось только реализовать формирование запроса и обработку ответа в нашем приложении. На стороне клиента на карте создаётся экземпляр MapClickListener и реализуется его метод onClick с параметром MapClickEvent, через который будут определяться методом getLonLat() текущие координаты x, y. Эти координаты, имя слоя и радиус поиска будут передаваться как входные параметры сервиса, который расширяет RemoteServiceServlet. Можно заготовить шаблон файла request.xml, в который будут вставляться значения этих параметров. Метод getSelectedFeatureInfo будет возвращать перечень семантических данных найденных объектов:
    @Override
    public List getSelectedFeatureInfo(double x, double y, double distance, String layer) {
        String urlStr = getServletContext().getInitParameter("geoserver-url") + "wfs";
        String requestTemplate = getServletContext().getRealPath("/") + "xml/request.xml";
        File file = new File(requestTemplate);
        List featuresInfo = null;
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document doc = builder.parse(file);
            Element root = doc.getDocumentElement();
            // Установка имени слоя
            NodeList list = root.getElementsByTagName("wfs:Query");
            NamedNodeMap attr = list.item(0).getAttributes();
            Node node = attr.getNamedItem("typeName");
            node.setNodeValue(layer);
            // Установка координат
            node = root.getElementsByTagName("gml:coordinates").item(0);
            node.setTextContent(x + "," + y);
            // Установка расстояния
            node = root.getElementsByTagName("ogc:Distance").item(0);
            node.setTextContent(String.valueOf(distance));
            // Установка соединения с GeoServer
            URL url = new URL(urlStr);
            URLConnection con = url.openConnection();
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setRequestProperty("Content-Type", "text/xml");
            // Формирование запроса
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            StringWriter out = new StringWriter();
            transformer.transform(new DOMSource(doc), new StreamResult(out));
            OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream());
            writer.write(out.toString());
            writer.flush();
            writer.close();
            // Получение ответа
            doc = builder.parse(con.getInputStream());
            root = doc.getDocumentElement();
            NodeList tip = root.getElementsByTagName("rptp:tip");
            NodeList nomer_tp = root.getElementsByTagName("rptp:nomer_tp");
            featuresInfo = new ArrayList(tip.getLength());
            for (int i = 0; i < tip.getLength(); i++) {
                String tipVal = tip.item(i).getTextContent();
                String nomer_tpVal = nomer_tp.item(i).getTextContent();
                FeatureInfo featureInfo = DB_RP.getFeatureInfo(tipVal, 
                    nomer_tpVal, getServletContext());
                if (featureInfo != null)
                    featuresInfo.add(i, featureInfo);
            }

        } catch (Throwable e) {
            e.printStackTrace();
        }
        return featuresInfo;
    }
В секции импорта следует добавить:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
Всё это работает! И не потребовалось устанавливать
OpenLayers.setProxyHost("gwtOpenLayersProxy?targetURL=");

Комментариев нет:

Отправить комментарий