{"id":9722,"date":"2024-10-21T09:00:00","date_gmt":"2024-10-21T12:00:00","guid":{"rendered":"https:\/\/www.fernandoquadro.com.br\/html\/?p=9722"},"modified":"2024-10-21T15:03:59","modified_gmt":"2024-10-21T18:03:59","slug":"wfs-t-com-openlayers-postgis-e-geoserver","status":"publish","type":"post","link":"https:\/\/www.fernandoquadro.com.br\/html\/2024\/10\/21\/wfs-t-com-openlayers-postgis-e-geoserver\/","title":{"rendered":"WFS-T com OpenLayers, PostGIS e GeoServer"},"content":{"rendered":"<p>Outro dia eu estava navegando pela internet quando &#8220;esbarrei&#8221; com um post muito interessante sobre como utilizar o WFS Transactions (WFS-T), para salvar fei\u00e7\u00f5es no PostGIS a partir de uma aplica\u00e7\u00e3o web com OpenLayers.<\/p>\n<p>Eu j\u00e1 tinha <a href=\"https:\/\/www.fernandoquadro.com.br\/html\/2018\/09\/18\/wfs-t-com-openlayers\/\" rel=\"noopener\" target=\"_blank\">postado algo bem similar<\/a> a isso a um bom tempo atr\u00e1s, mas j\u00e1 estava bastante desatualizado, ent\u00e3o eu decidir traduzir e (re)postar esse material aqui no blog. <\/p>\n<p><strong>1. O Projeto<\/strong><\/p>\n<p>Essa aplica\u00e7\u00e3o est\u00e1 <a href=\"https:\/\/github.com\/tcallsen\/react-openlayers-geoserver-postgis\" rel=\"noopener\" target=\"_blank\">postada no GitHub<\/a> e usa no frontend React\/OpenLayers para atualizar dados de recursos GIS armazenados em um banco de dados PostGIS usando transa\u00e7\u00f5es WFS (facilitadas pelo GeoServer).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-wfst-arch-layers.jpg.webp\" alt=\"\" width=\"890\" height=\"407\" class=\"aligncenter size-full wp-image-9723\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-wfst-arch-layers.jpg.webp 890w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-wfst-arch-layers.jpg-300x137.webp 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-wfst-arch-layers.jpg-768x351.webp 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-wfst-arch-layers.jpg-600x274.webp 600w\" sizes=\"auto, (max-width: 890px) 100vw, 890px\" \/><\/p>\n<p><strong>2. Objetivo<\/strong><\/p>\n<p>O objetivo era exibir um recurso WFS em um mapa com OpenLayers e gravar alguns dados no PostGIS cada vez que o recurso fosse clicado. Isso foi feito incluindo a propriedade <em>interation<\/em> nos dados do recurso que rastreou o n\u00famero de cliques.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-openlayers-feature-data-v2.jpg.webp\" alt=\"\" width=\"849\" height=\"495\" class=\"aligncenter size-full wp-image-9724\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-openlayers-feature-data-v2.jpg.webp 849w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-openlayers-feature-data-v2.jpg-300x175.webp 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-openlayers-feature-data-v2.jpg-768x448.webp 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-openlayers-feature-data-v2.jpg-600x350.webp 600w\" sizes=\"auto, (max-width: 849px) 100vw, 849px\" \/><\/p>\n<p>Foi utilizado o <a href=\"https:\/\/github.com\/kartoza\/docker-geoserver\/\" rel=\"noopener\" target=\"_blank\">docker kartoza\/docker-geoserver<\/a> para montar o backend com GeoServer e PostGIS. Gra\u00e7as ao trabalho duro do Kartoza, isso foi t\u00e3o f\u00e1cil quanto executar docker-compose up no diret\u00f3rio apropriado (<a href=\"https:\/\/github.com\/tcallsen\/react-openlayers-geoserver-postgis#start-and-configure-backend\" rel=\"noopener\" target=\"_blank\">mais instru\u00e7\u00f5es aqui<\/a>).<\/p>\n<p>Alguma configura\u00e7\u00e3o foi necess\u00e1ria para criar uma tabela e um registro de exemplo no PostGIS. Uma vez que isso foi conclu\u00eddo, mais algumas etapas foram necess\u00e1rias para criar um workspace, store e uma camada no GeoServer para publicar a tabela do PostGIS.<\/p>\n<div id=\"attachment_9725\" style=\"width: 1154px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-9725\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png.webp\" alt=\"\" width=\"1144\" height=\"730\" class=\"size-full wp-image-9725\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png.webp 1144w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png-300x191.webp 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png-1024x653.webp 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png-768x490.webp 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png-600x383.webp 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2024\/10\/tcallsen-geoserver-create-layer.png-945x603.webp 945w\" sizes=\"auto, (max-width: 1144px) 100vw, 1144px\" \/><p id=\"caption-attachment-9725\" class=\"wp-caption-text\">A etapa final \u00e9 publicar a camada no GeoServer e a\u00ed come\u00e7a a divers\u00e3o!<\/p><\/div>\n<p><strong>3. O Frontend<\/strong><\/p>\n<p>O aplicativo frontend foi baseado em React com OpenLayers. Alguns call-outs espec\u00edficos e li\u00e7\u00f5es aprendidas s\u00e3o compartilhados abaixo, mas confira o projeto no GitHub para o c\u00f3digo-fonte completo.<\/p>\n<p>3.1 Criando a camada WFS do GeoServer no OpenLayers<\/p>\n<p>Definir a camada e os estilos do WFS foi simples usando a estrat\u00e9gia bbox padr\u00e3o, usada para instruir o OpenLayers sobre como\/quando carregar os recursos do WFS. Veja: <\/p>\n<pre>\r\nimport VectorSource from 'ol\/source\/Vector';\r\nimport GeoJSON from 'ol\/format\/GeoJSON.js';\r\nimport {bbox as bboxStrategy} from 'ol\/loadingstrategy.js';\r\nimport VectorLayer from 'ol\/layer\/Vector';\r\n\r\nconst GEOSERVER_BASE_URL = 'http:\/\/localhost:8600\/geoserver\/dev';\r\n\r\n\/\/ create geoserver generic vector features layer\r\nconst featureSource = new VectorSource({\r\n  format: new GeoJSON(),\r\n  url: function (extent) {\r\n    return (\r\n      GEOSERVER_BASE_URL + '\/ows?service=WFS&' +\r\n      'version=1.0.0&request=GetFeature&typeName=dev%3Ageneric&maxFeatures=50&' + \r\n      'outputFormat=application%2Fjson&srsname=EPSG:3857&' +\r\n      'bbox=' +\r\n      extent.join(',') +\r\n      ',EPSG:3857'\r\n    );\r\n  },\r\n  strategy: bboxStrategy,\r\n});\r\n\r\nconst featureLayer = new VectorLayer({\r\n  source: featureSource,\r\n  style: {\r\n    'stroke-width': 0.75,\r\n    'stroke-color': 'white',\r\n    'fill-color': 'rgba(100,100,100,0.25)',\r\n  },\r\n});\r\n<\/pre>\n<p>3.2 Usando React Refs para acessar objetos OpenLayers<\/p>\n<p>Ao integrar o OpenLayers com o React, \u00e9 importante inicializar os objetos do OpenLayers uma vez (por exemplo, em um hook onload) e usar Refs para manter refer\u00eancias a esses objetos entre as renderiza\u00e7\u00f5es.<\/p>\n<p>Isso tamb\u00e9m permite que a vers\u00e3o atual desses objetos seja acess\u00edvel em fun\u00e7\u00f5es de retorno de chamada. Caso contr\u00e1rio, uma vers\u00e3o obsoleta do objeto pode ser fornecida ao retorno de chamada (capturada no momento do fechamento do retorno da chamada).<\/p>\n<pre>\r\n\/\/ react\r\nimport React, {  useEffect, useRef } from 'react';\r\nimport Map from 'ol\/Map'\r\n\r\nfunction MapWrapper(props) {\r\n\r\n  \/\/ refs are used instead of state to allow integration with 3rd party map onclick callback;\r\n  \/\/  these are assigned at the end of the onload hook\r\n  \/\/  https:\/\/stackoverflow.com\/a\/60643670\r\n  const mapRef = useRef();\r\n  const mapElement = useRef();\r\n  const featuresLayerRef = useRef();\r\n\r\n  \/\/ other logic removed for brevity\r\n\r\n  \/\/ react onload hook\r\n  useEffect( () => {\r\n\r\n    \/\/ create map\r\n    const map = new Map({\r\n      \/\/ config removed for brevity\r\n    })\r\n\r\n    \/\/ save map and featureLary references into React refs\r\n    featuresLayerRef.current = featureLayer;\r\n    mapRef.current = map\r\n\r\n  },[])\r\n\r\n  return (      \r\n    &lt;div&gt;\r\n      &lt;div ref={mapElement} className=\"map-container\"&gt;&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n  ) \r\n\r\n}\r\n\r\nexport default MapWrapper\r\n<\/pre>\n<p>No exemplo acima, os objetos OpenLayers <em>map<\/em>, <em>featuresLayer <\/em>e at\u00e9 mesmo o div <em>mapElement <\/em>s\u00e3o armazenados como Refs para uso em fun\u00e7\u00f5es de retorno de chamada fora do React.<\/p>\n<p>3.3 Executando transa\u00e7\u00f5es WFS a partir de fun\u00e7\u00f5es de retorno de chamada do OpenLayers<\/p>\n<p>O ponto crucial de todo esse aplicativo \u00e9 enviar as solicita\u00e7\u00f5es de transa\u00e7\u00e3o WFS para o GeoServer com os dados de recurso do OpenLayers para gravar no PostGIS. Isso \u00e9 tratado na <a href=\"https:\/\/github.com\/tcallsen\/react-openlayers-geoserver-postgis\/blob\/master\/src\/components\/MapWrapper.js#L147\" rel=\"noopener\" target=\"_blank\">fun\u00e7\u00e3o de retorno de chamada no onclick<\/a> do mapa.<\/p>\n<pre>\r\nimport WFS from 'ol\/format\/WFS';\r\nimport GML from 'ol\/format\/GML32';\r\n\r\nconst GEOSERVER_BASE_URL = 'http:\/\/localhost:8600\/geoserver\/dev';\r\n\r\n\/\/ map click handler - uses state and refs available in closure\r\nconst handleMapClick = async (event) => {\r\n\r\n  \/\/ get clicked feature from wfs layer\r\n  \/\/ TODO: currently only handles a single feature\r\n  const clickedCoord = mapRef.current.getCoordinateFromPixel(event.pixel);\r\n  const clickedFeatures = featuresLayerRef.current.getSource().getFeaturesAtCoordinate(clickedCoord);\r\n  if (!clickedFeatures.length) return; \/\/ exit callback if no features clicked\r\n  const feature = clickedFeatures[0];\r\n\r\n  \/\/ parse feature properties\r\n  const featureData = JSON.parse(feature.getProperties()['data']);\r\n\r\n  \/\/ iterate prop to test write-back\r\n  if (featureData.iteration) {\r\n    ++featureData.iteration;\r\n  } else featureData.iteration = 1;\r\n\r\n  \/\/ set property data back to feature\r\n  feature.setProperties({ data: JSON.stringify(featureData) });\r\n  console.log('clicked updated feature data', feature.getProperties())\r\n\r\n  \/\/ prepare feature for WFS update transaction\r\n  \/\/  https:\/\/dbauszus.medium.com\/wfs-t-with-openlayers-3-16-6fb6a820ac58\r\n  const wfsFormatter = new WFS();\r\n  const gmlFormatter = new GML({\r\n    featureNS: GEOSERVER_BASE_URL,\r\n    featureType: 'generic',\r\n    srsName: 'EPSG:3857' \/\/ srs projection of map view\r\n  });\r\n  var xs = new XMLSerializer();\r\n  const node = wfsFormatter.writeTransaction(null, [feature], null, gmlFormatter);\r\n  var payload = xs.serializeToString(node);\r\n\r\n  \/\/ execute POST\r\n  await fetch(GEOSERVER_BASE_URL + '\/wfs', {\r\n    headers: new Headers({\r\n      'Authorization': 'Basic ' + Buffer.from('admin:myawesomegeoserver').toString('base64'),\r\n      'Content-Type': 'text\/xml'\r\n    }),\r\n    method: 'POST',\r\n    body: payload\r\n  });\r\n\r\n  \/\/ clear wfs layer features to force reload from backend to ensure latest properties\r\n  \/\/  are available\r\n  featuresLayerRef.current.getSource().refresh();\r\n\r\n  \/\/ display updated feature data on map\r\n  setFeatureData(JSON.stringify(featureData));\r\n}\r\n<\/pre>\n<p>O c\u00f3digo acima \u00e9 executado quando o recurso WFS \u00e9 clicado. Isso aciona a seguinte l\u00f3gica:<\/p>\n<p><em>Linhas 11-14:<\/em> o objeto de fei\u00e7\u00e3o OpenLayers clicado \u00e9 identificado<br \/>\n<em>Linhas 17-25:<\/em> a propriedade <em>iteration<\/em> do recurso \u00e9 aumentada em 1 e salva de volta no recurso<br \/>\n<em>Linhas 30-38:<\/em> o recurso \u00e9 convertido no formato apropriado para a transa\u00e7\u00e3o WFS<br \/>\n<em>Linhas 41-48:<\/em> a solicita\u00e7\u00e3o de transa\u00e7\u00e3o WFS \u00e9 definida para a inst\u00e2ncia do Docker GeoServer criada anteriormente no projeto<br \/>\n<em>Linhas 52-55:<\/em> solicita que o OpenLayers recarregue os dados WFS do GeoServer para garantir que as propriedades de atualiza\u00e7\u00e3o estejam presentes<\/p>\n<p><strong>4. Para onde ir a partir daqui<\/strong><\/p>\n<p>Agora que temos um exemplo de como gravar dados GIS do OpenLayers no PostGIS, podemos expandir este aplicativo para suportar cria\u00e7\u00e3o e edi\u00e7\u00e3o de recursos mais complexos. Por exemplo, <a href=\"https:\/\/openlayers.org\/en\/latest\/examples\/draw-features.html\" rel=\"noopener\" target=\"_blank\">desenhar recursos com o OpenLayers<\/a>.<\/p>\n<p>Fonte: <a href=\"https:\/\/taylor.callsen.me\/save-openlayers-feature-data-to-postgis-using-wfs-transactions\/\" rel=\"noopener\" target=\"_blank\">Taylor Callsen<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Outro dia eu estava navegando pela internet quando &#8220;esbarrei&#8221; com um post muito interessante sobre como utilizar o WFS Transactions (WFS-T), para salvar fei\u00e7\u00f5es no PostGIS a partir de uma aplica\u00e7\u00e3o web com OpenLayers. Eu j\u00e1 tinha postado algo bem&#8230; <a class=\"more-link\" href=\"https:\/\/www.fernandoquadro.com.br\/html\/2024\/10\/21\/wfs-t-com-openlayers-postgis-e-geoserver\/\">Continue Reading &rarr;<\/a><\/p>\n","protected":false},"author":275,"featured_media":9731,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24],"tags":[208,250,212,326],"class_list":["post-9722","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gis","tag-geoserver","tag-openlayers","tag-postgis","tag-wfs-t"],"_links":{"self":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/9722","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/users\/275"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/comments?post=9722"}],"version-history":[{"count":7,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/9722\/revisions"}],"predecessor-version":[{"id":9744,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/9722\/revisions\/9744"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media\/9731"}],"wp:attachment":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media?parent=9722"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/categories?post=9722"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/tags?post=9722"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}