<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GIS &#38; Chips &#187; OpenLayers</title>
	<atom:link href="http://www.gisandchips.org/tag/openlayers/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.gisandchips.org</link>
	<description>Geografía útil para llevar</description>
	<lastBuildDate>Tue, 29 Nov 2011 10:38:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Digitalización web con OpenLayer y WFS-T (Geoserver)</title>
		<link>http://www.gisandchips.org/2010/09/16/digitalizacion-web-con-openlayer-y-wfs-t-geoserver/</link>
		<comments>http://www.gisandchips.org/2010/09/16/digitalizacion-web-con-openlayer-y-wfs-t-geoserver/#comments</comments>
		<pubDate>Thu, 16 Sep 2010 16:06:40 +0000</pubDate>
		<dc:creator>jose</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[Digitalización]]></category>
		<category><![CDATA[OpenLayers]]></category>
		<category><![CDATA[WFS-T]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=1654</guid>
		<description><![CDATA[OBJETIVO: En el contexto de la Web 2.0. cada vez son más los casos de cartografía interactiva donde continuamente se está actualizando la información. El caso más espectacular es el de OpenStreetMap, donde una legión de &#8220;mappers&#8221; interactuan con el sistema. Nuestro objetivo es mucho más modesto, pero también más fácil de implementar, y todo [...]]]></description>
			<content:encoded><![CDATA[<p>OBJETIVO:<br />
En el contexto de la Web 2.0. cada vez son más los casos de cartografía interactiva donde continuamente se está actualizando la información. El caso más espectacular es el de OpenStreetMap, donde una legión de &#8220;mappers&#8221; interactuan con el sistema. Nuestro objetivo es mucho más modesto, pero también más fácil de implementar, y todo gracias al uso de estándares y servicios de mapa libres. Perseguimos, en definitiva, una digitalización <em>on-line</em>, en todos los aspectos: creación de nuevos elementos, modificación geométrica de los ya existentes, actualización de atributos, etc. Todo ello con casi todas las ventajas de las aplicaciones de escritorio, pero con la singularidad de que cualquier usuario pueda intervenir sin apenas conocimientos. </p>
<p>Estos son los ingredientes para la receta:</p>
<ul>
<li>capas geográficas almacenadas en tablas PostgreSQL/PostGIS</li>
<li>servicio de mapas WFS-T con GeoServer</li>
<li>Diseño web con OpenLayers</li>
</ul>
<div id="attachment_1677" class="wp-caption aligncenter" style="width: 310px"><a href="http://www.gisandchips.org/wp-content/digitalizacion.png"><img src="http://www.gisandchips.org/wp-content/digitalizacion-300x298.png" alt="Digitalizacion WFS-T" width="300" height="298" class="size-medium wp-image-1677" /></a><p class="wp-caption-text">Digitalizacion WFS-T</p></div><br />
<span id="more-1654"></span><br />
En definitiva, nuestro objetivo es ofrecer un servicio de digitalización de elementos geométricos en Internet, utilizando como framework a OpenLayers, que carga una serie de capas servidas por un servicio de mapas de elementos transaccional (WFS-T) almacenadas en una geodatabase PostGIS.</p>
<p>Tengo que reconocer que en un primer momento intenté realizar este juego con un servidor WFS-T poco conocido, pero muy ligero (funciona como CGI, al igual que MapServer), y trabaja con PostGIS, con lo que me las prometía felices, pero las pruebas fueron satisfactorias  sólo mientras  modificase o crease geometría, pero en ningún momento pude subir o modificar atributos. Para los estudiosos este motor se llama TinyOWS . Seguro que más de uno lo habrá usado satisfactoriamente, con lo que se agradecerían comentarios de como hacerlo . Me gustaba porque la instalación era muy sencilla (colocar el ejecutable en el directorio donde se ejecutan los scripts CGI, generalmente en &#8220;cgi-bin&#8221;, sin necesidad de tener instaladas otras herramientas. En este enlace tienes un precioso ejemplo de esta implementación: <a href="http://dev4.mapgears.com/bdga/bdgaWFS-T.html">http://dev4.mapgears.com/bdga/bdgaWFS-T.html</a></p>
<p>Ante este dilema opté por instalar GeoServer (versión 2.0.1) cuya experiencia está más que demostrada para tener servicios de mapa interoperables. Sí quieres ver las características de nuestro servicio consúltalas aquí</p>
<p><a href="http://www.gisandchips.org:8080/geoserver/wfs?service=wfs&amp;VERSION=1.1.1&amp;REQUEST=GetCapabilities">http://www.gisandchips.org:8080/geoserver/wfs?service=wfs&amp;VERSION=1.1.1&amp;REQUEST=GetCapabilities</a></p>
<p>De esta manera me propuse crear un servicio WFS-T sobre tres capas, cada una de una tipología distinta ( puntos, polígonos y líneas), almacenadas en sus correspondientes tablas de PostGIS:</p>
<ul>
<li>tabla &#8220;golf&#8221; para puntos de campos de golf. Ver <a href="http://www.gisandchips.org:8080/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;TypeName=cite:golf">características.</a></li>
<li>tabla &#8220;municipios&#8221; para polígonos de términos municipales Ver <a href="http://www.gisandchips.org:8080/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;TypeName=cite:municipios">características.</a></li>
<li>tabla &#8220;vias&#8221; para línea de una red de carreteras Ver <a href="http://www.gisandchips.org:8080/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;TypeName=cite:vias">características.</a></li>
</ul>
<p>Para ver su potencial desarrollé tres páginas web para cada uno de los tipos de digitalización</p>
<ul>
<li>demo 1: <a href="http://www.gisandchips.org/demos/j3m/wfs/wfs_golf.html">Digitalización de Campos de golf</a></li>
<li>demo2: <a href="http://www.gisandchips.org/demos/j3m/wfs/wfs_municipios.html">Digitalización de municipios</a></li>
<li>demo 3: <a href="http://www.gisandchips.org/demos/j3m/wfs/wfs_vias.html">Digitalización de vías</a></li>
</ul>
<p><em>NOTA: Puedes probar sin miedo cualquiera de estas demos. Los datos se vuelven a cargar cada día. No te desesperes si es un poco lento el servidor web. No da más de sí.</em></p>
<p>En cada una de las demos he querido incorporar una barra de iconos para hacer uso de las siguientes herramientas:</p>
<p><div id="attachment_1673" class="wp-caption aligncenter" style="width: 212px"><a href="http://www.gisandchips.org/wp-content/panel.png"><img class="size-full wp-image-1673" src="http://www.gisandchips.org/wp-content/panel.png" alt="" width="202" height="29" /></a><p class="wp-caption-text">panel digitalización</p></div>
<ul>
<li>Consulta y edición de atributos</li>
<li>Digitalización</li>
<li>Mover elementos (o vértices en el caso de municipios y vías)</li>
<li>Borrar elementos</li>
<li>Guardar las transacciones</li>
<li>Navegación en modo Pan-zoom</li>
</ul>
<p>El código está implementado de tal forma que la digitalización forma parte de una sesión, y  sólo se validan los cambios (transacción) cuando hacemos clic en el botón &#8220;salvar&#8221;. De esta forma tengo recogido todo el abanico de posibilidades de la digitalización.</p>
<p>OPENLAYERS<br />
Este framework, de sobra conocido es el responsable de articular todos los flujos entre el cliente y la base de datos utilizando WFS-T servido por GeoServer. El código fuente de cada ejemplo lo puedes obtener del propio navegador, por lo que me centraré en desmenuzar las partes:</p>
<p>ESTILO HTML PARA MENÚS<br />
Estilo para la barra de iconos</p>
<pre class="brush: xml; title: ; notranslate">
    &lt;style&gt;
        .customEditingToolbar {
            float: right;
            right: 0px;
            height: 30px;
            width: 220px;
        }
        .customEditingToolbar div {
            float: right;
            margin: 5px;
            width: 24px;
            height: 24px;
        }
        .olControlNavigationItemActive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/editing_tool_bar.png&quot;);
            background-repeat: no-repeat;
            background-position: -103px -23px;
        }
        .olControlNavigationItemInactive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/editing_tool_bar.png&quot;);
            background-repeat: no-repeat;
            background-position: -103px -0px;
        }
        .olControlDrawFeaturePointItemInactive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/editing_tool_bar.png&quot;);
            background-repeat: no-repeat;
            background-position: -77px 0px;
        }
        .olControlDrawFeaturePointItemActive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/editing_tool_bar.png&quot;);
            background-repeat: no-repeat;
            background-position: -77px -23px ;
        }
        .olControlModifyFeatureItemActive {
            background-image: url(../../js/OpenLayers-2.8/theme/default/img/move_feature_on.png);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }
        .olControlModifyFeatureItemInactive {
            background-image: url(../../js/OpenLayers-2.8/theme/default/img/move_feature_off.png);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }
        .olControlDeleteFeatureItemActive {
            background-image: url(../../js/OpenLayers-2.8/theme/default/img/remove_point_on.png);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }
        .olControlDeleteFeatureItemInactive {
            background-image: url(../../js/OpenLayers-2.8/theme/default/img/remove_point_off.png);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }
        .olControlSelectFeatureItemInactive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/info_off.png&quot;);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }
        .olControlSelectFeatureItemActive {
            background-image: url(&quot;../../js/OpenLayers-2.8/theme/default/img/info_on.png&quot;);
            background-repeat: no-repeat;
            background-position: 0px 1px;
        }

    &lt;/style&gt;
</pre>
<p>Aplico en el código de OpenLayers los estilos que tendrá la capa WFS que vamos a cargar</p>
<pre class="brush: jscript; title: ; notranslate">
	    // Styles
            var styles = new OpenLayers.StyleMap({
                &quot;default&quot;: new OpenLayers.Style(null, {
                    rules: [
                        new OpenLayers.Rule({
                            symbolizer: {
                                &quot;Point&quot;: {
                                    pointRadius: 5,
                                    graphicName: &quot;square&quot;,
                                    fillColor: &quot;red&quot;,
                                    fillOpacity: 0.25,
                                    strokeWidth: 1,
                                    strokeOpacity: 1,
                                    strokeColor: &quot;#333333&quot;
                                }
                            }
                        })
                    ]
                }),
                &quot;select&quot;: new OpenLayers.Style({
                    strokeColor: &quot;#00ccff&quot;,
                    strokeWidth: 4
                }),
                &quot;temporary&quot;: new OpenLayers.Style(null, {
                    rules: [
                        new OpenLayers.Rule({
                            symbolizer: {
                                &quot;Point&quot;: {
                                    pointRadius: 5,
                                    graphicName: &quot;square&quot;,
                                    fillColor: &quot;white&quot;,
                                    fillOpacity: 0.25,
                                    strokeWidth: 1,
                                    strokeOpacity: 1,
                                    strokeColor: &quot;#333333&quot;
                                }

                            }
                        })
                    ]
                })
            });
</pre>
<p>En definitivo asigno un estilo para el elemento geométrico, y su comportamiento cuando es seleccionado, cuando está temporal (digitalización), o su valor por defecto.</p>
<p>CAPA WFS<br />
Definimos una capa WFS con Strategy (para indicar que conjunto de datos deben de ser cargados -ej. todos los datos, por BBOX, en clusters, por página, por filtro-)</p>
<pre class="brush: jscript; title: ; notranslate">
// Definimos una capa WFS con St
           var saveStrategy = new OpenLayers.Strategy.Save();

           wfs = new OpenLayers.Layer.Vector(&quot;golf&quot;, {
               //isBaseLayer:true,
               strategies: [new OpenLayers.Strategy.BBOX(), saveStrategy],
               projection: new OpenLayers.Projection(&quot;EPSG:23030&quot;),
               styleMap: styles,
               protocol: new OpenLayers.Protocol.WFS({
                   version: &quot;1.0.0&quot;,
                   srsName: &quot;EPSG:23030&quot;,
                   url: &quot;http://www.gisandchips.org:8080/geoserver/wfs&quot;,
                   featureType: &quot;golf&quot;,
                   featureNS: &quot;http://www.opengeospatial.net/cite&quot;,
                   geometryName: &quot;geometria&quot;,
                   schema: &quot;http://www.gisandchips.org:8080/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;TypeName=cite:golf&quot;,
               })
            });
 // Añadimos la capa anterior y los WMS que queramos de fondo
            map.addLayers([tiled,wfs]);
</pre>
<p>RESALTADO DE ELEMENTOS</p>
<p>El siguiente código controla el resaltado de los elementos cuando se lanzan eventos del ratón (mouseover, selección)</p>
<pre class="brush: jscript; title: ; notranslate">
		/* HIGHLIGHT */

	    // inicio highlight mouseover
            var report = function(e) {
                OpenLayers.Console.log(e.type, e.feature.id);
            };

            var highlightCtrl = new OpenLayers.Control.SelectFeature(wfs, {
                hover: true,
                highlightOnly: true,
                renderIntent: &quot;temporary&quot;,
                eventListeners: {
                    beforefeaturehighlighted: report,
                    featurehighlighted: report,
                    featureunhighlighted: report
                }
            });

            var selectCtrl = new OpenLayers.Control.SelectFeature(wfs,
                {clickout: true}
            );

            map.addControl(highlightCtrl);
            map.addControl(selectCtrl);

            highlightCtrl.activate();
            selectCtrl.activate();

	    /* END HIGHLIGHT */
</pre>
<p>SNAP O TOLERANCIAS DE CAZADO Y SPLIT (CORTES)</p>
<p>En ocasiones puede ser útil que la digitalización no sea a mano alzada (donde hagamos clic con el ratón), sino que tenga establecidas tolerancias de cazado para que cuando nos acerquemos a una línea o un polígono pueda pillar el nodo, vértice o eje. En este <a href="http://openlayers.org/dev/examples/snapping.html">enlace</a> verás un ejemplo más completo de este tema.<br />
En el caso del Split sólo tiene sentido cuando los elementos son polígonos o líneas. Esto nos permite por ejemplo crear una ruta (R1) desde cualquier punto de otra ruta existente (R2), y mientras con el snapping obtenemos la sujección al vértice o eje, la ruta ya existente (R2) se parte en dos partes.</p>
<pre class="brush: jscript; title: ; notranslate">
		/* SNAPPING */
            // Configuración del Snapping
            var snap = new OpenLayers.Control.Snapping({layer: wfs});
            map.addControl(snap);
            snap.activate();

            // Configuración del Split
            var split = new OpenLayers.Control.Split({
                layer: wfs,
                source: wfs,
                tolerance: 1000,
                deferDelete: true,
                eventListeners: {
                    aftersplit: function(event) {
                        var msg = &quot;Split resulted in &quot; + event.features.length + &quot; features.&quot;;
                        flashFeatures(event.features);
                    }
                }
            });
            map.addControl(split);
            split.activate();
</pre>
<p>PANEL Y CONTROLES</p>
<p>Como ya hemos comentado es preferible tener los iconos a mano en el mapa en vez de los típicos radio button, ya que resultan más elegantes.</p>
<pre class="brush: jscript; title: ; notranslate">
/* PANEL */
// crear un panel y añadirle iconos con funcionalidad
var panel = new OpenLayers.Control.Panel(
    {displayClass: 'customEditingToolbar'}
);

// Control para digitalizar puntos (draw)
var draw = new OpenLayers.Control.DrawFeature(
    wfs, OpenLayers.Handler.Point,
    {
        title: &quot;Draw Feature&quot;,
        displayClass: &quot;olControlDrawFeaturePoint&quot;,
        handlerOptions: {freehand: false, multi: false},
        // Muy importante: Si la capa postGIS es POINT poner multi:false.
        // Si es MULTIPOINT poner multi:true
        featureAdded: onFeatureInsert,
        //onFeatureInsert es el método que se ejecuta una vez que hemos terminado de digitalizar
    }
);

/*
En el caso de digitalizar polígonos este sería el código
            var draw = new OpenLayers.Control.DrawFeature(
                wfs, OpenLayers.Handler.Polygon,
                {
                    title: &quot;Draw Feature&quot;,
                    displayClass: &quot;olControlDrawFeaturePolygon&quot;,
                    handlerOptions: {multi: true},
                    featureAdded: onFeatureInsert,
                }
            );
*/

// Control para modificar elementos. En el caso de puntos sólo se pueden desplazar
// a otra posición
modify = new OpenLayers.Control.ModifyFeature(
    wfs, {displayClass: &quot;olControlModifyFeature&quot;, title: &quot;Modify Feature&quot;}
);

// Control para borrar elementos
var del = new DeleteFeature(wfs, {title: &quot;Delete Feature&quot;});

// Control para salvar la sesión de digitalización
var save = new OpenLayers.Control.Button({
    title: &quot;Save Changes&quot;,
    trigger: function() {
        if(modify.feature) {
            modify.selectControl.unselectAll();
        }
        saveStrategy.save();
    },
    displayClass: &quot;olControlSaveFeatures&quot;
});

/*
Control de Información. Es el típico control que una vez que seleccionas un elementos obtenemos los atributos, en este caso englobado en un popUp. Aquí hemos pensado reutilizar este formulario para actualizar datos de cada elemento (UPDATE de atributos).
*/
 selectControl = new OpenLayers.Control.SelectFeature(wfs,
 {
   onSelect: onFeatureInsert,
   onUnselect: onFeatureUnselect,
   displayClass: &quot;olControlSelectFeature&quot;,
   title: &quot;Info&quot;,
});

// Añadimos los controles al panel
panel.addControls([
    new OpenLayers.Control.Navigation({title: &quot;Pan and zoom&quot;}),
    save, del, modify, draw, selectControl
]);

// Definimos el control de navegación como el activo por defecto
panel.defaultControl = panel.controls[0];
map.addControl(panel);
</pre>
<p>FUNCIONES DE CADA CONTROL<br />
Borrar elementos</p>
<pre class="brush: jscript; title: ; notranslate">
 var DeleteFeature = OpenLayers.Class(OpenLayers.Control, {
      initialize: function(layer, options) {
          OpenLayers.Control.prototype.initialize.apply(this, [options]);
          this.layer = layer;
          this.handler = new OpenLayers.Handler.Feature(
              this, layer, {click: this.clickFeature}
          );
      },
      clickFeature: function(feature) {
          // sí no tiene fid eliminarlo
          if(feature.fid == undefined) {
              this.layer.destroyFeatures([feature]);
          } else {
              feature.state = OpenLayers.State.DELETE;
              this.layer.events.triggerEvent(&quot;afterfeaturemodified&quot;,
                                             {feature: feature});
              feature.renderIntent = &quot;select&quot;;
              this.layer.drawFeature(feature);
          }
      },
      setMap: function(map) {
          this.handler.setMap(map);
          OpenLayers.Control.prototype.setMap.apply(this, arguments);
      },
      CLASS_NAME: &quot;OpenLayers.Control.DeleteFeature&quot;
  });
</pre>
<p>Información de los elementos, actualización de atributos e inserción de nuevos elementos (geometría + atributos):</p>
<pre class="brush: jscript; title: ; notranslate">
// Funciones para cerrar el popUp
  function onPopupClose(evt) {
      selectControl.unselect(selectedFeature);
  }

// Función para cuando se quita la selección
 function onFeatureUnselect(feature) {
      map.removePopup(feature.popup);
      feature.popup.destroy();
      feature.popup = null;
  }
</pre>
<p>Función para insertar un nuevo elemento o actualizar atributos en uno ya existente</p>
<pre class="brush: jscript; title: ; notranslate">
// PopUp para insert/update
  function onFeatureInsert(feature)
  {
  		selectedFeature = feature;
                // mapeo de los atributos
		var fid = selectedFeature.id;
     	        var nombre = selectedFeature.attributes['nombre'];
     	        var municipio = selectedFeature.attributes['municipio'];
		var codive = selectedFeature.attributes['codive'];
		var hoyos = selectedFeature.attributes['hoyos'];
                // Compruebo si existe un valor para el atributo nombre.
                // Sí no tiene atributos es un &quot;INSERT&quot; y muestro el formulario para cumplimentar
		if (nombre == null)
		{
 			// formulario
	            htmlForm = &quot;&lt;div style='font-size:.8em'&gt;&quot;+
	            &quot;&lt;H2&gt;&lt;b&gt;A&amp;Ntilde;ADIR CAMPO DE GOLF&lt;/b&gt;&lt;/h2&gt;\n&quot; +
	            &quot;FID: &quot;+ fid + &quot;&lt;br/&gt;\n&quot; +
	            &quot;&lt;input type='hidden' name='fid' id='fid' value='&quot;+ fid +&quot;'&gt;&quot; +
	            //&quot;GEOMETRIA: &quot;+ feature.geometry + &quot;&lt;br/&gt;\n&quot; +
	            &quot;UBICACION: &quot;+ feature.geometry + &quot;&lt;br/&gt;\n&quot; +
	            &quot;Rellene los siguientes datos: &lt;br/&gt; \n&quot; +
	            &quot;NOMBRE:&lt;input type='text' name='nombre' id='nombre' value='Test Campo de Golf' size=20 style='background-color: #E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	            &quot;HOYOS:&lt;input type='text' name='hoyos' id='hoyos' value='18' size=2 style='background-color: #E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	            &quot;MUNICIPIO:&lt;input type='text' name='municipio' id='municipio' value='Test Municipio' size=20 style='background-color: #E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
		    &quot;IVE:&lt;input type='text' name='codive' id='codive' value='12345' size=5 style='background-color: #E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	            &quot;&lt;button onclick='onTriggerInsertar()'&gt;A&amp;ntilde;adir&lt;/button&gt;&quot; +
	            &quot;&lt;button onclick='onTriggerDelNewPoint()'&gt;Eliminar&lt;/button&gt;&lt;/div&gt;&quot;;
             }
/* Sí nombre tiene valor ofrezco los atributos en el formulario para modificarlos, añadiendo al popUp
    un botón para &quot;Actualizar&quot; el registro.
*/
                 else
            {
	         htmlForm = &quot;&lt;div style='font-size:.8em'&gt;&quot;+
	         &quot;&lt;H2&gt;&lt;b&gt;ACTUALIZAR CAMPO DE GOLF&lt;/b&gt;&lt;/h2&gt;\n&quot; +
	         &quot;FID: &quot;+ fid + &quot;&lt;br/&gt;\n&quot; +
	         &quot;&lt;input type='hidden' name='fid' id='fid' value='&quot;+ fid +&quot;'&gt;&quot; +
	         &quot;Rellene los siguientes datos: &lt;br/&gt; \n&quot; +
	         &quot;NOMBRE:&lt;input type='text' name='nombre' id='nombre' value='&quot;+ nombre + &quot;' size=20 style='background-color: #E6E6FA;color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	         &quot;HOYOS:&lt;input type='text' name='hoyos' id='hoyos' value='&quot; + hoyos + &quot;' size=2 style='background-color: #E6E6FA; color:#7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	         &quot;MUNICIPIO:&lt;input type='text' name='municipio' id='municipio' value='&quot; + municipio +&quot;' size=20 style='background-color:#E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
		 &quot;IVE:&lt;input type='text' name='codive' id='codive' value='&quot; + codive +&quot;' size=5 style='background-color:#E6E6FA; color: #7F7F7F; font-style: italic;' &gt; &lt;br/&gt;\n&quot; +
	         &quot;&lt;button onclick='onTriggerUpdate()'&gt;Actualizar&lt;/button&gt;&lt;/div&gt;&quot;;
      }
// Defino el objeto popUp
      popup = new OpenLayers.Popup.FramedCloud(&quot;info&quot;,
    		feature.geometry.getBounds().getCenterLonLat(),
         null,
         htmlForm,
         null,
         true,
         onPopupClose);

      feature.popup = popup;
      map.addPopup(popup);

  }

   // Trigger y función para pasar atributos del formulario
	var btnInsert = new OpenLayers.Control.Button({trigger: onTriggerInsertar});

	function onTriggerInsertar(fid)
	// se le pasa el fid como parametro para seleccionarlo mediante getFeatureById
	{
      //alert(&quot;has llegado a trigger insertar&quot;);
      var fid =  OpenLayers.Util.getElement('fid').value;// recojo el fid del formulario
      var miFeature = wfs.getFeatureById(fid);// hago la selección de la feature a partir del fid del form
      //alert(miFeature.id);
      // Paso atributos del formulario al elemento
		miFeature.attributes.nombre = OpenLayers.Util.getElement('nombre').value;
		miFeature.attributes.municipio = OpenLayers.Util.getElement('municipio').value;
		miFeature.attributes.hoyos = OpenLayers.Util.getElement('hoyos').value;
		miFeature.attributes.codive = OpenLayers.Util.getElement('codive').value;
		// elimina el popup
		onFeatureUnselect(miFeature);
	}
	// fin trigger insert

   // Trigger y función para pasar atributos del formulario en el update
	var btnUpdate = new OpenLayers.Control.Button({trigger: onTriggerUpdate});

	function onTriggerUpdate()
	{
      // defino miFeature como el array de elementos seleccionados, miFeature[0] será el único elemento del array
      miFeature = [selectedFeature]; //alert(miFeature.length);
      // Paso atributos del formulario al elemento
                var fid =  OpenLayers.Util.getElement('fid').value;
                miFeature[0].id = fid;
		miFeature[0].attributes.nombre = OpenLayers.Util.getElement('nombre').value;
		miFeature[0].attributes.municipio = OpenLayers.Util.getElement('municipio').value;
		miFeature[0].attributes.hoyos = OpenLayers.Util.getElement('hoyos').value;
		miFeature[0].attributes.codive = OpenLayers.Util.getElement('codive').value;
		// comprobar que se le pasan los atributos al feature. Descomentar para comprobación
		/*alert (  miFeature[0].id + &quot;,&quot; +
				   miFeature[0].attributes.nombre + &quot;,&quot; +
					miFeature[0].attributes.municipio  + &quot;,&quot; +
					miFeature[0].attributes.hoyos  + &quot;,&quot; +
					miFeature[0].attributes.codive );
               */

		miFeature[0].state = OpenLayers.State.UPDATE;
		// elimina el popup
		map.removePopup(miFeature[0].popup);
                selectControl.unselectAll();
                miFeature[0].popup = null;
	}
	// fin trigger
</pre>
<p>Faltan algunos elementos más, pero en esencia aquí está la lógica de la digitalización. Observa cada caso, puntos, polígonos y líneas para ver las diferencias existentes.</p>
<p>CONCLUSIÓN:<br />
Quizás lo más relevante de este tema es que con un sólo fichero HTML hemos abordado la complejidad de la digitalización, utilizando siempre servicios y tecnologías estándar y abiertas. Con este tipo de soluciones tan sencillas podemos acercar a un número mayor de usuarios, menos especializados quizás, pero que de otra forma se hubieran visto imposibilitados de actuar por razones técnicas, económicas o de formación. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2010/09/16/digitalizacion-web-con-openlayer-y-wfs-t-geoserver/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>OpenLayers y Panoramio</title>
		<link>http://www.gisandchips.org/2010/05/04/openlayers-y-panoramio/</link>
		<comments>http://www.gisandchips.org/2010/05/04/openlayers-y-panoramio/#comments</comments>
		<pubDate>Tue, 04 May 2010 09:59:47 +0000</pubDate>
		<dc:creator>jose</dc:creator>
				<category><![CDATA[Programación]]></category>
		<category><![CDATA[OpenLayers]]></category>
		<category><![CDATA[Panoramio]]></category>

		<guid isPermaLink="false">http://www.gisandchips.org/?p=1601</guid>
		<description><![CDATA[Tras leer el excelente post de Pepe relativo a la integración de servicios de geo-localización en un ambiente Google Maps llegué a la conclusión de que resulta muy fácil aplicarlo con dicha API, pero mi inquietud me llevó a investigar en la posibilidad de integrarlo en otra API de tipo mapping de reconocido prestigio, como [...]]]></description>
			<content:encoded><![CDATA[<p>Tras leer el excelente <a href="/2010/03/24/api-de-google-openstreetmap-y-otros-servicios-georreferenciados/">post</a> de <a href="http://www.gisandchips.org/author/pepe/">Pepe</a> relativo a la integración de servicios de geo-localización en un ambiente <a href="/2010/03/24/api-de-google-openstreetmap-y-otros-servicios-georreferenciados/">Google Maps</a> llegué a la conclusión de que resulta muy fácil aplicarlo con dicha API, pero mi inquietud me llevó a investigar en la posibilidad de integrarlo en otra API de tipo mapping de reconocido prestigio, como es el caso de <a href="http://openlayers.org">OpenLayers</a>, que además es 100% Open Source, lo que nos permite añadir múltiples fuentes de datos (wms, wfs, gml, xml, geojson, georss, conjunto de tiles, proveedores de mapa mundiales -OpenStreetMap, GMaps, Virtual Earth, Yahoo Maps, etc.), además de esa libertad de movimientos que le confieren una cierta ventaja sobre su homónima, sin que ello suponga una crítica hacia Google Maps, que entre otras virtudes ha popularizado el uso de los servicios de mapa vía web, acercándolo a un público genérico sin apenas conocimientos.<br />
<div id="attachment_1618" class="wp-caption aligncenter" style="width: 496px"><a href="http://www.gisandchips.org/demos/j3m/panoramio/panoramio.html"><img src="http://www.gisandchips.org/wp-content/captura_panoramio2.png" alt="Openlayers con Panoramio" width="486" height="228" class="size-full wp-image-1618" /></a><p class="wp-caption-text">Openlayers con Panoramio</p></div><br />
<span id="more-1601"></span></p>
<p>A la hora de &#8220;copiar&#8221; el ejemplo descrito en dicho post para OpenLayer, no he planteado integrar todos los geoservicios, entre otras cosas porque algunos de ellos forman parte de la API casi desde su inicio, y no supone un gran esfuerzo incorporarlo, salvo el de YouTube, que no tuve tiempo de ver, aunque tampoco creo que haya muchos videos geoposicionados. Ejemplos de integración de KML en OL hay muchos en la red, al igual que de GML, GeoJSON o GeoRSS, y también de Flickr, que en el fondo es acceder a un documento XML. Para más información consultas los <a href="http://dev.openlayers.org/releases/OpenLayers-2.9/examples/">ejemplos</a> on-line del portal Openlayers.org</p>
<p>De todos los geoservicios, al que más atención ha prestado ha sido al de <a href="http://www.panoramio.com">Panoramio</a>, entre otras cosas por la ingente cantidad de fotografías georreferenciadas que integra, lo que lo hace muy atractivo para el webmaster. Como muchos sabéis este portal dispone de un <a href="http://www.panoramio.com/api/">servicio REST</a> público que nos permite ejecutar una URL con ciertos parámetros, que finalmente nos devuelve un documento estructurado en formato JSON</p>
<pre>
{
"count": 110,
"photos": [
{
"upload_date": "20 May 2007",
"owner_name": "Carlos Sieiro del Nido",
"photo_id": 2313090,
"longitude": -0.516679,
"height": 75,
"width": 100,
"photo_title": "UNIVERSIDAD DE ALICANTE-Campus de San Vicente del Raspeig.",
"latitude": 38.386611000000002,
"owner_url": "http://www.panoramio.com/user/134716",
"owner_id": 134716,
"photo_file_url": "http://mw2.google.com/mw-panoramio/photos/thumbnail/2313090.jpg",
"photo_url": "http://www.panoramio.com/photo/2313090"
},
{
"upload_date": "16 January 2007",
"owner_name": "\u00a9 www.fotoseb.es - Sebastien Pigneur Jans",
"photo_id": 453228,
"longitude": -0.51438300000000003,
"height": 66,
"width": 100,
"photo_title": "Foto Aerea Universidad Alicante 2 (Foto_Seb)",
"latitude": 38.381700000000002,
"owner_url": "http://www.panoramio.com/user/55833",
"owner_id": 55833,
"photo_file_url": "http://mw2.google.com/mw-panoramio/photos/thumbnail/453228.jpg",
"photo_url": "http://www.panoramio.com/photo/453228"
}]</pre>
<p>}</p>
<p>Como podéis ver por cada foto disponemos de un array con los datos que nos interesan, y de forma particular los siguientes:</p>
<ul>
<li> <strong>owner_name</strong>: autor</li>
<li> <strong>photo_title</strong>: título</li>
<li> <strong>latitude</strong> y <strong>longitude</strong>: coordenadas geográficas de geoposicionamiento en datum WGS 84</li>
<li> <strong>photo_id</strong>: identificador de foto</li>
<li> <strong>photo_file_url</strong>: URL de la imagen</li>
<li> <strong>photo_url</strong>: URL del recurso en la web de Panoramio</li>
</ul>
<p>Integrar Panoramio en OpenLayers no resulta tan elegante como en GM, donde sólo hay que escribir 2 líneas, pero con un poco de esfuerzo y el apoyo de la comunidad seguro que alguien es capaz de escribir una <a href="http://docs.openlayers.org/library/formats.html#creating-custom-formats">clase derivada</a> de JSON que facilite las cosas. Por ahora me limitaré a desmenuzar el código de forma secuencial para que se entienda el proceso</p>
<p>Lo primero que debemos de hacer es preparar nuestro proxy para que admita peticiones al host de Panoramio.  En este <a href="http://trac.openlayers.org/wiki/FrequentlyAskedQuestions#ProxyHost">enlace</a> explica como hacerlo</p>
<pre class="brush: jscript; title: ; notranslate">
OpenLayers.ProxyHost= &quot;/cgi-bin/proxy.cgi?url=&quot;;
</pre>
<p>Posteriormente creamos un par de variables con la URL del servicio REST y los parámetros</p>
<pre class="brush: jscript; title: ; notranslate">
url = &quot;http://www.panoramio.com/map/get_panoramas.php&quot;;
parametros = {
   order:'popularity',
   set:'full',
   from:0,
   to:20,
   minx: minx,
   miny: miny,
   maxx: maxx,
   maxy: maxy,
   size:'thumbnail'
}
</pre>
<p>Consulta la documentación de la API de Panoramio para más información. Por ahora los parámetros que más nos interesan son:</p>
<ul>
<li> “<strong>to</strong>”: indica el nº de fotografías que queremos cargar. Máximo 50</li>
<li> “<strong>minx</strong>”,”<strong>miny</strong>”,”<strong>maxx</strong>”,”<strong>maxy</strong>”: coordenadas de la BBOX donde buscará fotos. Debén de estar en coordenadas geográficas (EPSG: 4326). En mi caso los he obtenido utilizando este código:</li>
</ul>
<pre class="brush: jscript; title: ; notranslate">
// Obtener extensiones.
ext = map.getExtent();
var minx = ext.left;
var miny = ext.bottom;
var maxx = ext.right;
var maxy = ext.top; //alert (minx + &quot; &quot; + miny + &quot; &quot; + maxx + &quot; &quot; + maxy);
</pre>
<p>Por último sólo nos queda invocar a un HttpRequest de AJAX que ejecute la URL con los parámetros y los envíe a nuestra función: mostrarfotos</p>
<pre class="brush: jscript; title: ; notranslate">
OpenLayers.loadURL(url, parametros, this, mostrarfotos);
</pre>
<p><strong>Función mostrarfotos</strong><br />
Es la encargada de procesar el documento JSON que recibe como argumento</p>
<pre class="brush: jscript; title: ; notranslate">
function mostrarfotos(response) {
</pre>
<p>El documento JSON, que todavía es una cadena de texto es parseada con OpenLayers.Format.JSON, que la convierte en un objeto que ya podemos tratar:</p>
<pre class="brush: jscript; title: ; notranslate">
var json = new OpenLayers.Format.JSON();
var panoramio = json.read(response.responseText);
</pre>
<p>Creamos un array de featutes con el total de fotos  (panoramio.photos.length)</p>
<pre class="brush: jscript; title: ; notranslate">
var features = new Array(panoramio.photos.length);
</pre>
<p>Aplicamos un bucle “for” para recorrer cada fotografía.</p>
<pre class="brush: jscript; title: ; notranslate">
for (var i = 0; i &lt; panoramio.photos.length; i++)
{
</pre>
<p>Declaramos y almacenamos cada dato de la foto en una variable para recorrerla con comodidad.</p>
<pre class="brush: jscript; title: ; notranslate">
var upload_date = panoramio.photos[i].upload_date;
var owner_name = panoramio.photos[i].owner_name;
var photo_id = panoramio.photos[i].photo_id;
var longitude =panoramio.photos[i].longitude;
var latitude = panoramio.photos[i].latitude;
var pheight = panoramio.photos[i].height;
var pwidth = panoramio.photos[i].width;
var photo_title = panoramio.photos[i].photo_title;/
var owner_url = panoramio.photos[i].owner_url;
var owner_id = panoramio.photos[i].owner_id;
var photo_file_url = panoramio.photos[i].photo_file_url;
var photo_url = panoramio.photos[i].photo_url;
</pre>
<p>Opcionalmente podemos recoger todas las fotografías en código HTML para ser ubicadas en el BODY con un DIV, en forma de “galería de fotos”</p>
<pre class="brush: jscript; title: ; notranslate">
OpenLayers.Util.getElement('galeria').innerHTML +=
     &quot;&lt;a href='&quot;+ photo_url +
     &quot;'&gt;&lt;img src='&quot;+photo_file_url+&quot;' title='&quot;+
     photo_title +&quot;' /&gt;&lt;/a&gt;&amp;nbsp;&quot;;
</pre>
<p>Posteriormente en el BODY para representar esta galería sólo hay que escribir:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;div id=&quot;galeria&quot;&gt;&lt;/div&gt;
</pre>
<p>Creamos un objeto point con las coordenadas de la foto</p>
<pre class="brush: jscript; title: ; notranslate">
var fpoint = new OpenLayers.Geometry.Point(longitude,latitude);
</pre>
<p>Creo un array de atributos</p>
<pre class="brush: jscript; title: ; notranslate">
var atributos = {
   'upload_date' : upload_date,
   'owner_name':owner_name,
   'photo_id':photo_id,
   'longitude':longitude,
   'latitude':latitude,
   'pheight':pheight,
   'pwidth':pwidth,
   'pheight':pheight,
   'photo_title':photo_title,
   'owner_url':owner_url,
   'owner_id':owner_id,
   'photo_file_url':photo_file_url,
   'photo_url':photo_url
}
</pre>
<p>Por cada foto creo un elemento (feature) que contiene su posición y sus atributos</p>
<pre class="brush: jscript; title: ; notranslate">
features[i] = new OpenLayers.Feature.Vector(fpoint,atributos);
</pre>
<p>Terminamos el bucle</p>
<pre class="brush: jscript; title: ; notranslate">
}
</pre>
<p>Fuera del bucle, todavía en la función mostrar foto nos queda:</p>
<p>Definir el estilo para representar la foto. Tenemos dos opciones<br />
a) Crear un icono único para cada fotografía</p>
<pre class="brush: jscript; title: ; notranslate">
// estilo punto
var panoramio_style1 = new OpenLayers.StyleMap(OpenLayers.Util.applyDefaults({
   pointRadius: 15,
   fillColor: &quot;red&quot;,
   fillOpacity: 1,
   strokeColor: &quot;black&quot;,
   externalGraphic: &quot;${photo_file_url}&quot;
}, OpenLayers.Feature.Vector.style[&quot;default&quot;]));
</pre>
<p>b) Por cada posición añadimos una miniatura de la foto.</p>
<pre class="brush: jscript; title: ; notranslate">
var panoramio_style2 = new OpenLayers.StyleMap(OpenLayers.Util.applyDefaults({
   pointRadius: 7,
   fillColor: &quot;red&quot;,
   fillOpacity: 1,
   strokeColor: &quot;black&quot;,
   externalGraphic: &quot;panoramio-marker.png&quot;
}, OpenLayers.Feature.Vector.style[&quot;default&quot;]));
</pre>
<p>Imagen: Ejemplos de estilos</p>
<div id="attachment_1613" class="wp-caption aligncenter" style="width: 310px"><a href="http://www.gisandchips.org/wp-content/captura_panoramio_styles.png"><img class="size-medium wp-image-1613" src="http://www.gisandchips.org/wp-content/captura_panoramio_styles-300x123.png" alt="Estilos para Panoramio" width="300" height="123" /></a><p class="wp-caption-text">Estilos para Panoramio</p></div>
<p>Ahora creamos la vector layer asignándole el estilo escogido</p>
<pre class="brush: jscript; title: ; notranslate">
var vectorPano = new OpenLayers.Layer.Vector(&quot;Panoramio fotos&quot;, {
   styleMap: panoramio_style2
});
</pre>
<p>Añadimos los elementos:</p>
<pre class="brush: jscript; title: ; notranslate">
vectorPano.addFeatures(features);
</pre>
<p>Añadimos la layer al mapa</p>
<pre class="brush: jscript; title: ; notranslate">
map.addLayer(vectorPano);
</pre>
<p>Definimos para terminar la función el comportamiento del evento que se desencadenará cuando seleccionemos ( click con el ratón) cada fotografía</p>
<pre class="brush: jscript; title: ; notranslate">
selectControl = new OpenLayers.Control.SelectFeature(vectorPano,
   {onSelect: onFeatureSelect, onUnselect: onFeatureUnselect});
map.addControl(selectControl);
selectControl.activate();
}
</pre>
<p>Como ves, se ejecutará la función “onFeatureSelect” cada vez que se selecciona un feature, y “onFeatureUnselect” cuando no hay nada seleccionado.</p>
<p>Con este código tenemos toda la lógica para obtener un vector layer con su posición y atributos. Ahora sólo nos queda definir el comportamiento de los eventos. En este caso vamos a crear un “popup” por cada elemento.</p>
<pre class="brush: jscript; title: ; notranslate">
// popups
function onPopupClose(evt) {
   selectControl.unselect(selectedFeature);
}
function onFeatureSelect(feature) {
   selectedFeature = feature;

   // HTML del PopUp
      mensaje = &quot;&lt;a href='http://www.panoramio.com'&gt;&lt;img src='./panoramio_header-logo.png' alt='Panoramio' width='146' height='27' /&gt;&lt;/a&gt;&lt;br&gt;&quot;+
	      &quot;&lt;h2&gt;&quot;+ feature.attributes.photo_title + &quot;&lt;/h2&gt;&lt;p&gt;&quot; +
	      &quot;&lt;a href='&quot;+ feature.attributes.photo_url+
		  &quot;'&gt;&lt;img src='http://mw2.google.com/mw-panoramio/photos/small/&quot; +
		  feature.attributes.photo_id + &quot;.jpg' border='0' alt=''&gt;&lt;/a&gt;&lt;br&gt;&quot; +
	      &quot;autor: &lt;a href='&quot;+ feature.attributes.owner_url+&quot;'&gt;&quot;+feature.attributes.owner_name +&quot;&lt;/a&gt;&quot;;

   popup = new OpenLayers.Popup.FramedCloud(&quot;chicken&quot;,
      feature.geometry.getBounds().getCenterLonLat(),
      null,
      mensaje,
      null, true, onPopupClose);
   feature.popup = popup;
   map.addPopup(popup);
}
function onFeatureUnselect(feature) {
   map.removePopup(feature.popup);
   feature.popup.destroy();
   feature.popup = null;
}
</pre>
<p>Ya lo tenemos todo listo. Sólo nos queda verlo en un navegador. ¿A que esperas?</p>
<p><a href="http://www.gisandchips.org/demos/j3m/panoramio/panoramio.html">Ver demo en proyección Mercator con fondo de OpenStreetMap</a><br />
<a href="http://www.gisandchips.org/demos/j3m/panoramio/panoramio_4326.html">Ver demo en WGS84</a></p>
<p><strong>Nota de interés:</strong></p>
<blockquote><p>
Por último nos gustaría hacer una consideración de especial relevancia. Este código tiene sentido cuando todas las layers (WMS y vector layer de Panoramio) se encuentran en la misma proyección, en este caso se utiliza el datum WGS84 en coordenadas geográficas (EPSG: 4326). En el caso de que utilices en el mapa otra proyección debes de recordar que has de pasarle a la URL de Panoramio los parámetros de caja en WGS84, y lo mismo ocurre cuando el servicio te devuelve los puntos de cada foto, que ahora debes de hacer lo contrario, es decir, convertirla de EPSG:4326 a la proyección que estés utilizando.
</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.gisandchips.org/2010/05/04/openlayers-y-panoramio/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

