miércoles, 24 de septiembre de 2014

Programación y Personalización SIG. Programación SIG en entornos web

1.1. La Web y el HTML

La World Wide Web, que se abrevia WWW y se conoce también como la Web, es un sistema de páginas enlazadas mediante el concepto de hipertexto. El hipertexto es un texto que refiere a otro texto al que el lector puede tener acceso inmediato. El lenguaje tradicional y predominante sobre el que se sustenta la Web y que tiene capacidades de hipertexto es el HTML (HyperText Markup
Language, ‘lenguaje de marcas de hipertexto’). 

Hasta hace relativamente poco era necesario conocer el lenguaje HTML para poder publicar una página web en Internet. Si bien el HTML es un lenguaje muy directo, en el que buena parte del código es el contenido propiamente dicho del documento, como contrapartida es engorroso de escribir. Así, por ejemplo, para mostrar una palabra en negrita, es necesario intercalar en el texto
más de media docena de caracteres extras.

1.2. Elementos y etiquetas

El lenguaje HTML se utiliza para describir la estructura y el contenido de un documento, así como para complementar el texto con objetos (por ejemplo, mapas) e imágenes. 

Un típico código HTML está formado por elementos que configuran las distintas partes del documento. Generalmente, estos elementos están delimitados por una etiqueta de inicio y otra de fin, que se escriben siempre entre corchetes angulares ("<" y ">").

Por ejemplo, la línea siguiente:

<p>La WWW nació en el CERN.</p>

define un elemento (párrafo) cuyo contenido es “La WWW nació en el CERN.”. Como podemos ver, las etiquetas de inicio y fin (<p> y </p> respectivamente) delimitan el contenido del párrafo. Además, la etiqueta de fin es igual que la de inicio pero precedida por una barra (“/”).

1.3. Estructura mínima de un documento HTML

Un documento HTML bien formado debe presentar siempre la siguiente estructura mínima:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="es">
<head>
<title>Título del documento</title>
</head>
<body>
Contenido del documento.
</body>
</html>

Como podemos observar, la estructura mínima de un documento HTML está formada por cinco elementos:
• DOCTYPE. Especifica la versión del lenguaje HTML que obedece el documento (en el ejemplo, la 4.01).
• HTML. Es el elemento raíz, el que define el documento HTML en sí. Contiene obligatoriamente los elementos head y body. Como puede apreciarse en el ejemplo, el elemento html puede contener el atributo lang, que define el idioma del documento (“es” de español).
• HEAD. Define la cabecera del documento, cuyo cometido es alojar información
sobre el mismo. El título, las palabras clave y otros datos que no se consideran parte del contenido del documento irán dentro de este elemento.
• TITLE. Define el título del documento.
• BODY. Define el contenido del documento. Este elemento contendrá otros que definirán el  contenido del documento

1.4. Definición del contenido
El contenido de un documento HTML puede definirse mediante tres tipos de elementos:
• Estructurales. Son aquellos que describen la estructura del texto. En HTML tenemos elementos estructurales para definir títulos, párrafos, tablas, enumeraciones, etc. Normalmente, los navegadores aplican diferentes estilos de presentación a cada elemento.
• De presentación. Son aquellos que describen el aspecto del texto, independientemente de su función. Los elementos que definen negritas, cursivas, texto enfatizado, etc. son elementos de presentación.
• De hipertexto. Son aquellos que permiten enlazar con otros documentos mediante la creación de hipervínculos. Son los más importantes dada la naturaleza hipertextual de la Web.

2. Google Maps

Google Maps es la herramienta SIG en línea de Google. Permite explorar mapas en 2D de casi cualquier parte del mundo. La popularidad de la que goza Google Maps en la actualidad se debe principalmente a su buen rendimiento, su vasta cartografía y la posibilidad de incorporar toda esta tecnología a cualquier sitio web mediante el uso de la API* (de Application Programming Interface,
‘interfaz de programa de aplicación’) de Google Maps. En los apartados siguientes, veremos cómo incorporar un mapa a nuestra página y cómo hacer uso de la API para trabajar sobre él.

2.1. Incorporar un mapa en una página
La forma más sencilla de incorporar un mapa en nuestra página es utilizando el web de Google Maps y, una vez seleccionada la ubicación que deseamos mostrar, incorporar el código HTML que nos proporciona Google a nuestra página. 

Veámoslo paso a paso. En primer lugar, debemos seleccionar una ubicación en maps.google.es. Esto se puede hacer mediante la caja de búsqueda o navegando por el mapa.

En segundo lugar, hemos de seleccionar la opción Enlazar y copiar el código HTML que aparece en la caja “Pegar HTML para insertar en sitio web”.

El tercer y último paso será incorporar este código en nuestra página HTML:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Nationaal Park De Hoge Veluwe</title>
</head>
<body>
<p>El <em>Nationaal Park De Hoge Veluwe</em> (el Parque
Nacional <em>De Hoge Veluwe</em> es un parque nacional
neerlandés situado en la provincia de Gelderland, cerca
de las ciudades de Ede, Arnhem y Apeldoorn.</p>
<iframe width="425" height="350" frameborder="0"
scrolling="no" marginheight="0" marginwidth="0"
src="http://maps.google.com/?hl=es&amp;ie=UTF8 
&amp;ll=52.075286,5.889359&amp;spn=0.261235,0.698318 
&amp;z=11&amp;output=embed">
</iframe><br />
<small><a href="http://maps.google.com/?hl=es 
&amp;ie=UTF8&amp;ll=52.075286,5.889359 
&amp;spn=0.261235,0.698318&amp;z=11&amp;source=embed"
style="color:#0000FF;text-align:left">Ver mapa más
grande</a></small>
</body>
</html>

2.2. La API de Google Maps

Afortunadamente, Google Maps nos ofrece muchas más posibilidades que las que hemos visto hasta ahora. Sin embargo, éstas se obtienen a costa de la facilidad de uso, pues se requieren unos mínimos conocimientos de programación para poder sacarles partido.
A esta funcionalidad adicional se accede por medio de la API de Google Maps, mediante un conjunto de tecnologías agrupadas bajo la denominación AJAX (Asynchronous JAvascript and XML, ‘JavaScript asíncrono y XML’). Como su nombre indica, AJAX está compuesto por dos tecnologías:

• JavaScript. Es un lenguaje muy parecido a Java que se usa para alterar documentos HTML de forma dinámica. Es importante remarcar que no es Java; sólo está basado en él. 
• XML. Es un lenguaje usado para el intercambio de datos (sigla de eXtensible Markup Language, ‘lenguaje de marcas extensible’). Es una forma genérica de escribir documentos maximizando su simplicidad, su generalidad y su usabilidad.
Ambas tecnologías, integradas en un documento HTML, permiten obtener mapas y datos de los servidores de Google Maps y mostrarlos en la página. 

2.2.1. Crear un mapa
La función más elemental que nos ofrece la API de Google Maps es crear un mapa. El mapa se mostrará de forma muy parecida a cuando se copia el código HTML del sitio de Google Maps: centrado en un punto y con un nivel de zoom determinado. Además, se mostrarán una serie de controles mínimos que nos permitirán desplazarnos por el mapa, variar su ampliación y seleccionar el tipo de mapa mostrado. 

<!DOCTYPE html "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type"
content="text/html; charset=utf-8"/>
<title>Universitat Oberta de Catalunya</title>
<script src="http://maps.google.com/maps?file=api 
&amp;v=2&amp;key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
type="text/javascript"></script>
<script type="text/javascript">
function InicializarMapa() {
var mapa;
var centro;
if (GBrowserIsCompatible()) {
mapa = new GMap2(
document.getElementById("elemento_mapa"));
centro = new GLatLng(41.414829, 2.133003);
mapa.setCenter(centro, 17);
mapa.setUIToDefault();
}
}
</script>
</head>
<body onload="InicializarMapa()" onunload="GUnload()">
<div id="elemento_mapa"
style="width: 500px; height: 300px"></div>
</body>
</html>

Como podemos observar, el código es una mezcla entre HTML y JavaScript. El código JavaScript (que hemos resaltado convenientemente mediante recuadros) se incluye mediante el elemento script y siempre va situado en la cabecera del documento (head). Observad el uso del atributo “type” (‘tipo’) delelemento script, que identifica el código como JavaScript.

También es importante observar las similitudes entre Java y JavaScript. Quizá la diferencia más notable sea la ausencia de tipos. Todas las variables son del mismo tipo, aunque como ya veremos, internamente puedan tener atributos distintos. Consecuentemente, las variables se declaran usando la fórmula var nombre_variable y las funciones, function nombre_función.

Veamos los pasos que hay que seguir para mostrar un mapa en nuestra página: en primer lugar, es necesario crear un elemento HTML que albergue el mapa. Aunque en la práctica hay diversos elementos que pueden desempeñar esta función, es recomendable utilizar div, porque es el elemento más neutro. Al elemento utilizado debemos darle un nombre mediante el atributo “id” (de identifier, ‘identificador’) y, adicionalmente, un tamaño, que especificaremos mediante los atributos estilísticos width (ancho) y height (alto), y que será el tamaño del mapa medido en píxeles. En el ejemplo, se ha creado el elemento mediante el código siguiente: 

<div id="elemento_mapa"
style="width: 500px; height: 300px"></div>

Como se puede observar, al elemento se le ha llamado “elemento_mapa”, y se le han atribuido las dimensiones iniciales de 500 por 300 píxeles. 

Una vez creado el elemento que albergará el mapa, debemos escribir el código JavaScript que se encargará de inicializarlo y mostrarlo. Para llevar a cabo esta tarea, hemos de realizar tres acciones:
 
• incluir el código de la API de Google Maps, 
• escribir una función que se encargue de inicializar el mapa, y
• llamar a la función de inicialización cuando el navegador termine de dibujar la página.
 
Veámoslas una a una:
 
Incluir el código de la API de Google Maps
Mediante la primera acción, damos acceso a nuestro código a la API de Google Maps. Esta acción se realiza en el primer bloque de código JavaScript:

<script src="http://maps.google.com/maps?file=api 
&amp;v=2&amp;key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
type="text/javascript"></script>

Por regla general, este código siempre será el mismo. Su única peculiaridad es el valor “key” (que en el ejemplo ha sido reemplazado por varias equis), que variará de un lugar web a otro y que se debe obtener de Google mediante un proceso de registro gratuito*. Una vez hecho esto, el código JavaScript de nuestra página podrá acceder a las funciones de la API de Google Maps

Escribir una función que se encargue de inicializar el mapa

La segunda acción crea el mapa sobre el elemento HTML seleccionado y lo inicializa, estableciendo el centro y el nivel de zoom iniciales. En el ejemplo, esta acción la lleva a cabo la función InicializarMapa en el segundo bloque de código JavaScript:
<script type="text/javascript">
function InicializarMapa() {
var mapa;
var centro;
if (GBrowserIsCompatible()) {
mapa = new GMap2(
document.getElementById("elemento_mapa"));
centro = new GLatLng(41.414829, 2.133003);
mapa.setCenter(centro, 17);
mapa.setUIToDefault();
}
}
</script>

Como se puede observar, en primer lugar instanciamos un objeto “GMap2”, que es el mapa en sí, ubicándolo en el elemento HTML “elemento_mapa”, que es el que hemos creado al principio con este objetivo. Después, establecemos el centro y el nivel de zoom iniciales del mapa, mediante la función setCenter de la clase “GMap2”. Como primer argumento, esta función requiere un objeto
“GLatLng”, que no es más que una coordenada geográfica expresada en función de su latitud y su longitud (según el sistema de coordenadas WGS84). El segundo elemento es un entero, que puede variar entre 0 y 19, por los niveles de zoom mínimo y máximo respectivamente. 

La llamada a la función GBrowserIsCompatible evita la ejecución del código en caso de que se detecte que el navegador utilizado no es compatible con Google Maps.

Llamar a la función de inicialización cuando el navegador termine de dibujar la página
 
Finalmente, sólo queda indicar al navegador cuándo se debe inicializar el mapa o, lo que es lo mismo, cuándo debe ejecutarse la función InicializarMapa.

Mientras un navegador carga una página HTML y la construye, se obtienen, a la vez, imágenes externas y otros scripts que puedan formar parte de la página. Durante este proceso de carga, el entorno puede no ser lo bastante estable para Google Maps, y ello podría conducir a un comportamiento errático de la aplicación. Google recomienda inicializar el mapa sólo cuando la página haya sido cargada completamente. 

Para ello, utilizaremos el atributo “onload” del elemento HTML body. En él podemos introducir código JavaScript que se ejecutará cuando la página haya sido cargada completamente. En nuestro caso, pondremos una llamada a la función InicializarMapa:

<body onload="InicializarMapa()" onunload="GUnload()">

De la misma forma, cuando se descarga la página, cosa que sucede cuando se cambia a otra, llamaremos a la función GUnload de la API de Google Maps para liberar correctamente los recursos que haya consumido el mapa. El resultado será el siguiente:



Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/. 
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización SIG, Albert Gavarró Rodríguez , Universidad Oberta  de Catalunya, España 2011

Programación y Personalización SIG.Adaptación y extensión de un SIG

gvSIG 
gvSIG es una herramienta orientada al manejo de información geográfica. Permite acceder a información vectorial y rasterizada georreferenciada, así como a servidores de mapas que cumplan las especificaciones que impone el OGC (Open Geospatial Consortium —Consorcio Abierto Geoespacial—). Las razones para usar gvSIG son variadas:

• Está programado en Java, lo que significa que puede ejecutarse virtualmente en casi cualquier máquina.
• Es software libre*, lo que nos da la libertad de modificarlo y distribuirlo a nuestro antojo. El coste de este software es cero.
• Su funcionalidad se puede ampliar fácilmente mediante complementos que se pueden programar en Java o en Jython**.
• Está probado por una base amplia de usuarios, lo que asegura una buena estabilidad del código

1.1. Arquitectura de gvSIG
gvSIG se estructura alrededor de un núcleo de funcionalidad relativamente pequeño. A este núcleo, que contiene las partes esenciales del sistema, se le pueden añadir complementos para dotarlo de nuevas funcionalidades.

Esta arquitectura permite descomponer el sistema en partes pequeñas, más fáciles de implementar y mantener. Actualmente, gran parte de la distribución de gvSIG son complementos. Aunque los complementos pueden ser de naturaleza muy variopinta, siempre se muestran al usuario de forma homogénea.
Es decir, el aspecto gráfico y la forma de trabajar con un complemento u otro serán más o menos parecidos.

Los complementos se integran en el marco de gvSIG mediante las llamadas “extensiones”, que no son más que un conjunto de clases Java que añaden nuevas funcionalidades a la aplicación.

El núcleo de gvSIG está formado por tres subsistemas:

FMap. Agrupa la lógica SIG. Contiene las clases que se encargan de generar los mapas, gestionar las capas, transformar coordenadas y realizar búsquedas, consultas, análisis, etc.

gvSIG. Agrupa las clases encargadas de gestionar la interfaz gráfica de la aplicación y representar en pantalla los mapas generados por el subsistema FMap. En otras palabras, este subsistema se encarga de proporcionar una interfaz de usuario que proporciona acceso a las funcionalidades de gvSIG

Subdriver. Contiene las clases que se encargan de dar acceso a los datos, tanto locales como remotos (por ejemplo, servicios OGC). Además, permite escribir datos geográficos en los formatos más comunes.

Para llevar a cabo su cometido, un complemento puede acceder a uno o más subsistemas de gvSIG

1.2. Anatomía de un complemento

Como mínimo, un complemento está constituido por: • un fichero XML que describe el complemento, y • el código del complemento. El fichero XML, que debe llamarse config.xml, proporciona a gvSIG la información necesaria sobre el complemento para que pueda cargarlo e integrarlo en la interfaz gráfica.

Entre otras cosas, especifica el directorio que contiene el có- digo del complemento, las dependencias que tiene con otros complementos y los elementos de interfaz de usuario (menús, barras de herramientas, etc.) que añade a la interfaz de gvSIG. El código del complemento es un conjunto de clases Java que implementan la funcionalidad aportada por el complemento.

Probablemente, el código irá acompañado de ficheros de configuración, imágenes, datos geográficos o cualquier otro dato que sea necesario para la ejecución de éste. A lo largo de este módulo, vamos a implementar, a modo de ejemplo, un complemento llamado “Construcciones” que mostrará elementos constructivos (edificios y caminos) sobre una base topográfica. 

En los apartados siguientes, aprenderemos a crear un complemento y, por medio de él, a: 

• agregar una capa de información geográfica a una vista,
• dibujar líneas y polígonos georeferenciados sobre un mapa, y 
• crear una herramienta que muestre información sobre los elementos dibujados. 

1.3. El fichero config.xml

El fichero config.xml contiene los datos de configuración del complemento. Este fichero, que obligatoriamente debe acompañar al código, define básica- CC BY • PID_00174756 9 Adaptación y extensión de un SIG mente los elementos de interfaz de usuario que se van a añadir a la aplicación (herramientas, menús y controles de la barra de estado), a la vez que los asocia con clases del código. Cada una de estas asociaciones es una “extensión”. 

El fichero config.xml debe escribirse en lenguaje XML (de eXtensible Markup Language —‘lenguaje de marcas extensible’—) y ha de seguir unas pautas determinadas. Un documento XML está constituido por elementos que forman una jerarquía. Estos elementos se denotan mediante el uso de etiquetas de inicio y fin. Las etiquetas tienen la forma , o , en donde nombre es el nombre del elemento. La primera es una etiqueta de inicio, la segunda de fin, y la última una combinación de ambas. Las dos primeras se utilizan cuando un elemento puede contener otros elementos, y sirven para marcar el inicio y el fin de dicho elemento. 

La última está reservada a elementos que no contienen otros. Además, los elementos pueden tener atributos cuyos valores se establecen de forma muy parecida a las asignaciones de los lenguajes de programación. Veamos el fichero de configuración que usará nuestro complemento: 

<?xml version="1.0" encoding="ISO-8859-1"?>
<plugin-config>
<depends plugin-name="com.iver.cit.gvsig" />
<libraries library-dir="."/>
<extensions>
<extension class-name=
"edu.uoc.postgradosig. construcciones.Construcciones"
description="Complemento que dibuja construcciones"
active="true"
priority="50">
<menu text="Postgrado SIG/Construcciones"
<action-command="MENU_CONSTRUCCIONES" />
</extension>
</extensions>
</plugin-config>

La primera línea, llamada prólogo, describe la versión del lenguaje XML utilizada para escribir el documento y su codificación. Como el prólogo no forma parte de la configuración del complemento, no debemos preocuparnos demasiado por su contenido. Simplemente, siempre utilizaremos la misma fórmula.

El significado del resto de elementos es el siguiente:

plugin-config. Es una etiqueta que engloba todas las opciones de configuración. Es la raíz de la jerarquía de elementos.
depends. Enumera los complementos de los que depende nuestro complemento para funcionar correctamente. El nombre del complemento se especifica mediante el atributo “plugin-name”. Debe haber un elemento “depends” por cada complemento. Un complemento depende de otro cuando el código del primero utiliza alguna de las clases del código del segundo. Normalmente, todos los complementos dependerán del complemento gvSIG (“com.iver.cit.gvsig”), que es la aplicación en sí. libraries. Especifica el directorio en el que reside el código de nuestro complemento. El nombre del directorio se especifica mediante el atributo “library-dir”. El punto como nombre de directorio significa que el código se encuentra en el mismo directorio que el fichero de configuración.  extensions. Marca el inicio de la lista de extensiones del complemento.
extension. Declara una extensión de gvSIG. La declaración incluye el nombre de la clase Java que implementa la extensión y una lista de elementos de interfaz de usuario que deben añadirse a la aplicación y que están asociados con la extensión. El elemento “extension” tiene los atributos siguientes:

class-name: especifica el nombre de la clase que implementa la extensión.
description: describe la funcionalidad que aporta la extensión. Esta etiqueta es meramente informativa.
active: especifica si la extensión está activa o inactiva. Si el valor de este atributo es “false”, gvSIG no cargará la extensión.
priority: establece la prioridad de carga de la extensión respecto al resto de extensiones declaradas en el fichero de configuración. Cuanto menor sea el número, antes se cargará la extensión. En nuestro caso, sólo hay una extensión, por lo que el valor de la prioridad no es fundamental.

menu. Define una nueva entrada del menú de gvSIG y la asocia a la extensión. El elemento “menu” tiene dos atributos:
text: establece el nombre y la ubicación del menú. Puede usarse la barra (“/”) para establecer diversos niveles de menú.
action-command: especifica el identificador de la acción. Cada vez que se active el menú, se llamará a la clase asociada pasándole el valor de este atributo. Esto nos permite asociar varios menús a una sola clase. CC BY • PID_00174756 11 Adaptación y extensión de un SIG

En el ejemplo, se declara la opción de menú Postgrado SIG /Construcciones con el identificador asociado “MENU_CONSTRUCCIONES”. El resultado será el siguiente:

 1.4. La clase “Extensión”
Una vez creado el fichero XML de configuración de la extensión, en cuyo contenido hemos definido la clase que la implementa y los elementos de interfaz de usuario que nos permitirán acceder a su funcionalidad, llega el momento de implementarla.

Para ser consideradas como tales, las extensiones deben extender (valga la redundancia) la clase “Extension”* y sobrecargar sus funciones. Al reconocer la clase como una extensión, gvSIG se comunicará con ella por medio de estas funciones, que actuarán a modo de interfaz entre gvSIG y la extensión. 

A continuación, se muestra una posible implementación de la extensión “Construcciones”:

package edu.uoc.postgradosig.construcciones;
import com.iver.andami.plugins.Extension;
import javax.swing.JOptionPane;
public class Construcciones extends Extension {
public void initialize() {
// aquí se realizan las tareas de inicialización
}
public boolean isEnabled() {
return true; // está habilitada
}

public boolean isVisible() {
return true; // es visible
}
public void execute(String actionCommand) {
// muestra un mensaje por pantalla
JOptionPane.showMessageDialog(null,
"Esta extensión muestra elementos constructivos.");
}
}

Como se puede observar, la clase “Construcciones” extiende la clase “Extension” e implementa (sobrecarga) cuatro funciones: initialize, isEnabled, isVisible y execute. El cometido de cada una de ellas es el siguiente:

void initialize(). Se utiliza para inicializar la extensión. En esta función emplazaremos llamadas a funciones que se encarguen de reservar recursos, leer configuraciones, etc. Debemos fijarnos en que esta función desempeña un papel muy parecido al del constructor de la clase. Sin embargo, por coherencia con gvSIG, reservaremos las tareas más arduas para esta función, limitando el cometido del constructor —si lo hay— a la inicialización de variables. 
boolean isEnabled(). Indica si la extensión debe considerarse habilitada o no. Cuando una extensión no está habilitada, los elementos de interfaz de usuario (botones, menús o controles de la barra de estado) que tiene asociados no son accesibles. En la mayoría de sistemas, estos elementos se mostrarán en tonos grisáceos. gvSIG llama periódicamente a esta función respondiendo a los eventos de la interfaz gráfica. 
boolean isVisible(). Indica si los elementos de interfaz de usuario que tiene asociados la extensión deben estar visibles o no. Si esta función devuelve false, no se mostrará ninguno de sus elementos. gvSIG llama periódicamente a esta función respondiendo a los eventos de la interfaz gráfica. 
void execute(String actionCommand). gvSIG llama a esta función cada vez que se activa la extensión, es decir, cada vez que el usuario accede a alguno de los elementos de interfaz de usuario que la extensión ha definido. El parámetro “actionCommand” contiene el valor del atributo “action-command” (definido en el fichero de configuración) del elemento al que se ha accedido. 

Todas las extensiones están obligadas a implementar estas cuatro funciones. Al tratarse de funciones sobrecargadas, debemos prestar especial atención a respetar tanto los modificadores de visibilidad como el tipo de la función y de los parámetros. Opcionalmente, si se desea un mayor control sobre los procesos de inicialización y descarga de la extensión, también se pueden implementar las funciones siguientes:

void postInitialize(). Se llama a esta función después de haberse llamado a la función initialize de todas las extensiones. Se utilizará para realizar tareas de inicialización que requieran una interacción con otras extensiones. 
void terminate(). Se llama a esta función al terminar la ejecución de gvSIG. Típicamente, en esta función almacenaremos las preferencias del usuario, liberaremos recursos, etc.

Volvamos al ejemplo: la extensión mostrará una ventana con un mensaje descriptivo cada vez que se active. Este código, combinado con el fichero de configuración que ya habíamos definido, dará lugar a que se muestre el mensaje cada vez que se seleccione la opción de menú Postgrado SIG / Construcciones. Cabe destacar que, en este caso, el contenido del parámetro “actionCommand” será “MENU_CONSTRUCCIONES”, tal y como habíamos definido en el fichero de configuración. También es importante observar que las funciones isEnabled e isVisible devuelven true, lo que indica en todo momento que la extensión está habilitada y es visible. Debemos observar también que el mensaje informativo se muestra mediante la función showMessageDialog de la clase “JOptionPane”*. El resultado será el siguiente:

1.5 Crear una capa vectorial en memoria
Para crear un mapa vectorial en memoria, usaremos la clase “ConcreteMemoryDriver”. Las líneas siguientes: 

ConcreteMemoryDriver mapa;
mapa = new ConcreteMemoryDriver();
mapa.setShapeType(FShape.LINE + FShape.POLYGON);
mapa.getTableModel().setColumnIdentifiers(new String[] {
"ID", "Descripcion"});

crean un mapa vectorial llamado “mapa”. Además, se establecen, mediante la función setShapeType, los tipos de figuras que contendrá, “FShape.LINE” (por lí- neas) y “FShape.POLYGON” (por polígonos), y mediante la función setColumnIdentifiers, los atributos asociados a cada una de ellas, “ID” y “Descripcion”. Los atributos se pueden definir arbitrariamente según nuestras necesidades

1.6.Dibujar líneas y polígonos
Una vez creado el mapa, podemos empezar a añadirle figuras. Supongamos que, en una fase previa de captación de datos, hemos localizado una vivienda y un camino.
La vivienda la dibujaremos mediante el objeto “FPolygon2D”. El código siguiente:

GeneralPathX ruta;
FPolygon2D edificio;
// definimos la geometría del edificio
ruta = new GeneralPathX();
ruta.moveTo(356270.7, 4727507.1); // mueve el cursor al inicio
ruta.lineTo(356273.0, 4727503.3); // dibuja el primer segmento
ruta.lineTo(356278.5, 4727505.8); // dibuja el segundo segmento
ruta.lineTo(356275.5, 4727509.8); // dibuja el tercer segmento
ruta.lineTo(356270.7, 4727507.1); // dibuja el cuarto segmento
// creamos el objeto edificio a partir de la geometría definida
// previamente
edificio = new FPolygon2D(ruta);
// agregamos el edificio al mapa
mapa.addShape(edificio, new Object[] {
// valor del atributo "ID"
ValueFactory.createValue(1),
// valor del atributo "Descripcion"
ValueFactory.createValue("Casa unifamiliar.") }); 

dibuja la vivienda y la agrega al mapa. Como se puede observar, primero usamos un objeto de la clase “GeneralPathX” para definir la geometría de la figura, y después creamos el polígono mediante la clase “FPolygon2D”. En un último paso, lo agregamos al mapa y especificamos el valor de sus atributos. Observad que la función createValue está sobrecargada, lo que permite pasarle por parámetro tanto enteros como texto. También debemos notar que trabajamos directamente con coordenadas geográficas. El camino lo dibujaremos de forma muy parecida. La única diferencia es que utilizaremos la clase “FPolyline2D” en lugar de “FPolygon2D”: 

GeneralPathX ruta;
FShape camino;
// definimos la geometría del edificio
ruta = new GeneralPathX();
ruta.moveTo(356242.7, 4727498.8);
ruta.lineTo(356268.0, 4727509.8);
ruta.lineTo(356281.0, 4727519.1);
// creamos el objeto camino a partir de la geometría
// definida previamente
camino = new FPolyline2D(ruta);
// agregamos el camino al mapa
mapa.addShape(camino, new Object[] {
ValueFactory.createValue(2),
ValueFactory.createValue("Camino de herradura.") });  

1.7 Crear una capa
Una vez dibujado el mapa, ya sólo nos queda agregarlo a la vista actual como una capa. Esta tarea se reparte entre las clases “LayerFactory”, que crea la capa, y “MapControl”, que la agrega a la vista actual: 

FLayer capa;
MapControl controlMapa;
View vistaActual;
// obtiene la ventana de vista activa
vistaActual = (View)PluginServices.getMDIManager().
getActiveWindow();
// obtiene el objeto que controla el mapa
controlMapa = vistaActual.getMapControl();
// crea una capa a partir de un mapa
capa = LayerFactory.createLayer("Construcciones", mapa,
controlMapa.getProjection());
// hace visible la capa
capa.setVisible(true);
// agrega la capa a la vista actual
controlMapa.getMapContext().getLayers().addLayer(capa); 


Como se puede observar, la capa se crea mediante la función createLayer de la clase “LayerFactory”. Esta función espera tres argumentos: el nombre de la capa (“Construcciones”), el mapa que contendrá y la proyección con la que se representará (la misma que se utiliza en la vista). En un segundo paso, se agrega la capa a la vista mediante la función addLayer. Como podemos ver, para poder llamar a la función necesitamos obtener, en primer lugar, el contexto del mapa (“getMapContext”), y en segundo lugar, la colección de las capas que contiene (“getLayers”)

Recapitulando, el código de la extensión nos queda así:  


package edu.uoc.postgradosig.construcciones;
import com.hardcode.gdbms.engine.values.ValueFactory;
import com.iver.andami.PluginServices;
import com.iver.andami.plugins.Extension;
import com.iver.andami.ui.mdiManager.IWindow;
import com.iver.cit.gvsig.fmap.MapControl;
import com.iver.cit.gvsig.fmap.core.FPolygon2D;
import com.iver.cit.gvsig.fmap.core.FPolyline2D;
import com.iver.cit.gvsig.fmap.core.FShape;
import com.iver.cit.gvsig.fmap.core.GeneralPathX;
import com.iver.cit.gvsig.fmap.drivers.
ConcreteMemoryDriver;
import com.iver.cit.gvsig.fmap.layers.FLayer;
import com.iver.cit.gvsig.fmap.layers.LayerFactory;
import com.iver.cit.gvsig.project.documents.view.gui.View;
public class Construcciones extends Extension {
public void execute(String actionCommand) {
ConcreteMemoryDriver mapa;
GeneralPathX ruta;
FShape edificio, camino;
FLayer capa;
MapControl controlMapa;
View vistaActual;
// crea un mapa vectorial en memoria
mapa = new ConcreteMemoryDriver();
mapa.setShapeType(FShape.LINE + Fshape.POLYGON);
mapa.getTableModel().setColumnIdentifiers(
new String[] { "ID", "Descripcion"});
// definimos la geometría del edificio
ruta = new GeneralPathX();
ruta.moveTo(356270.7, 4727507.1);
ruta.lineTo(356273.0, 4727503.3);
ruta.lineTo(356278.5, 4727505.8);
ruta.lineTo(356275.5, 4727509.8);
ruta.lineTo(356270.7, 4727507.1);
// creamos el objeto edificio a partir de la geometría
// definida previamente
edificio = new Fpolygon2D(ruta);
// agregamos el edificio al mapa
mapa.addShape(edificio, new Object[] {
ValueFactory.createValue(1),
ValueFactory.createValue("Casa unifamiliar.") });
// definimos la geometría del camino
ruta = new GeneralPathX();
ruta.moveTo(356242.7, 4727498.8);
ruta.lineTo(356268.0, 4727509.8);
ruta.lineTo(356281.0, 4727519.1);
// creamos el objeto camino a partir de la geometría
// definida previamente
camino = new Fpolyline2D(ruta);
// agregamos el camino al mapa
mapa.addShape(camino, new Object[] {
ValueFactory.createValue(2),
ValueFactory.createValue("Camino de herradura.") });
// crea la capa
vistaActual = (View)PluginServices.getMDIManager().
getActiveWindow();
controlMapa = vistaActual.getMapControl();

capa = LayerFactory.createLayer("Construcciones", mapa,
controlMapa.getProjection());
capa.setVisible(true);
// agrega la capa a la vista actual
controlMapa.getMapContext().getLayers().addLayer(capa);
}
public boolean isEnabled() {
IWindow ventanaActiva;
// obtiene la ventana activa
ventanaActiva = PluginServices.getMDIManager().
getActiveWindow();
// estará habilitada sólo si la ventana actual es una
// ventana de vista
return ventanaActiva instanceof View;
}
public boolean isVisible() {
// siempre estará visible
return true;
}
public void initialize() {
}
}

Como podemos apreciar, la vista contiene dos capas: la base topográfica (“TGSPNIRS.jpg”) y la capa “Construcciones” que acabamos de crear. Mediante las clases “GPolygon2D” y “GPolyline2D” hemos conseguido dibujar un edificio y un camino al este del pueblo de Noarre.

1.8. El viewport 
Cuando se muestra un mapa por pantalla, no siempre se muestra en toda su extensión. Que veamos una parte mayor o menor del mismo depende del nivel de zoom. De hecho, el área visible y el nivel de zoom se rigen por una regla de proporcionalidad inversa: a mayor zoom, menor área, y a menor zoom, mayor área. El recuadro que delimita el área visible de un mapa se llama viewport. Normalmente, se define por las coordenadas geográficas de sus esquinas inferior izquierda (suroeste) y superior derecha (noreste). Latitud y longitud crecen desde la coordenada suroeste hasta la coordenada noreste.

gvSIG nos da acceso al viewport mediante la función getViewPort de la clase “MapControl”. El viewport es un objeto importante en gvSIG, pues nos proporciona mecanismos para transformar las coordenadas de la pantalla en coordenadas geográficas y viceversa. Esto nos servirá, por ejemplo, para determinar las coordenadas de un punto seleccionado mediante el ratón. Suponiendo que las variables x e y (de tipo int) son las componentes de un punto del viewport, el código siguiente:

View vistaActiva;
MapControl controlMapa;
ViewPort viewport;
Point2D geoPoint;
// obtiene el viewport
vistaActiva = (View)PluginServices.getMDIManager().
getActiveWindow();
controlMapa = vistaActiva.getMapControl();
viewport = controlMapa.getViewPort();
// transforma las coordenadas del viewport en coordenadas
// geográficas
geoPoint = viewport.toMapPoint(x, y);
JOptionPane.showMessageDialog(null, “Latitud: “ +
geoPoint().getX() + “ Longitud: “ +
geoPoint.getY());

muestra por pantalla las coordenadas geográficas que se corresponden con este punto. Como se puede observar, la función que hace la transformación es toMapPoint. Esta función devuelve un objeto “Point2D” que contiene las coordenadas geográficas resultantes, cuyas componentes se pueden consultar mediante las funciones getY (latitud) y getX (longitud). Otras funciones interesantes que nos ofrece la clase viewport son las siguientes:

fromMapPoint. Hace la transformación inversa: de coordenadas geográficas a coordenadas del viewport.
fromMapDistance. Transforma una distancia geográfica en una distancia del viewport. • toMapDistance. Transforma una distancia del viewport en una distancia geográfica. • getAdjustedExtend. Devuelve los límites geográficos del viewport, es decir, las coordenadas de las esquinas.
 
 1.9. Agregar un botón a la barra de herramientas
 El primer paso será agregar un botón a la barra de herramientas de la aplicación. Los botones, como todo elemento gráfico, pueden añadirse mediante el fichero de configuración config.xml. Las líneas siguientes

 <tool-bar name="Postgrado SIG" position="2">
<action-tool icon="seleccion.png"
tooltip="Localizador" position="1"
action-command="BOTON_LOCALIZADOR"/>
</tool-bar>

 añaden un botón a la barra de herramientas de gvSIG. El significado de cada uno de los elementos se detalla a continuación: 
tool-bar. Define una barra de herramientas. Este elemento tiene dos atributos: – name: especifica el nombre de la barra de herramientas. – position: especifica la posición que ocupará la barra de herramientas entre las demás. 
action-tool. Define un botón de la barra de herramientas. Este elemento, que debe ir siempre dentro de un elemento “tool-bar”, tiene cuatro atributos: – icon: especifica el nombre del fichero que contiene la imagen que aparecerá en el botón. – tooltip: especifica el texto que se mostrará para describir la herramienta. – position: especifica la posición del botón dentro de la barra de herramientas. 
action-command: al igual que el atributo “action-command” de los menús, define un identificador para la acción (este identificador se utilizará en las llamadas a la función exec de la extensión). El elemento “tool-bar” debe ir siempre dentro de un elemento “extension”. Si incorporamos estas líneas al fichero de configuración de la extensión “Construcciones”, el resultado será el siguiente:

<?xml version="1.0" encoding="ISO-8859-1"?>
<plugin-config>
<depends plugin-name="com.iver.cit.gvsig" />
<libraries library-dir="."/>
<extensions>
<extensionclass-name=
"edu.uoc.postgradosig.construcciones.Construcciones"
description="Complemento que dibuja construcciones"
active="true"
priority="50">
<menu text="Postgrado SIG/Construcciones"
action-command="MENU_CONSTRUCCIONES" />
<tool-bar name="Postgrado SIG" position="2">
<action-tool icon="consulta.png"
tooltip="Consultar construcciones"
position="1"
action-command="BOTON_CONSTRUCCIONES"/>
</tool-bar>
</extension>
</extensions>
</plugin-config>

Es importante observar que el atributo “action-command” del menú y del botón son diferentes: en el primer caso es MENU_CONSTRUCCIONES, mientras que en el segundo caso es BOTON_CONSTRUCCIONES. Esto permite a la extensión (mediante el parámetro que recibe en la función execute) averiguar por medio de qué elemento de la interfaz de usuario ha sido llamada: si por medio del menú o por medio del botón de la herramienta. El efecto sobre la interfaz de usuario de gvSIG será el siguiente:



Como se puede observar, se añade un botón “C” a la barra de herramientas.

 

Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/. 
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización SIG, Albert Gavarró Rodríguez , Universidad Oberta  de Catalunya, España 2011

Programación y Personalización SIG.La API de Java



El lenguaje Java, en su distribución estándar, viene acompañado de una extensa API* (de Application Programming Interface, ‘interfaz de programación de aplicaciones’) que proporciona un conjunto de clases ya implementadas que facilitan o resuelven los problemas más habituales con los que se encuentra un programador.

Las clases de la API de Java, que estan estructuradas en paquetes, ofrecen, entre otras, las siguientes funcionalidades: • lectura y escritura de ficheros en el paquete “java.io”,

 • representación de gráficos: dibujo de líneas, polígonos, elipses, etc., en el paquete “java.awt”,
 • creación de interfaces gráficas de usuario en el paquete “javax.swing”, y
• gestión y ordenación de objetos mediante pilas, colas, listas, etc., en el paquete "java.util" .

Todas estas clases se pueden importar en nuestro código mediante la directiva import. Por ejemplo, el código siguiente hace uso de la clase “Vector” para implementar una cola de tareas que se deben realizar:

import java.util.Vector; // importamos la clase Vector
class Tarea {
private String descripción; // descripción de la tarea
// constructor de la clase Tarea: su único parámetro es
// la descripción (textual) de la tarea
public Tarea(String desc) {
descripción = desc;
}
// devuelve la descripción de la tarea
public String obtDescripción() {
return descripción;
}
public static void main(String[] args) {
// el Vector “tareas” almacenará las tareas en orden
Vector tareas;
Tarea tarea;  


// crea una cola de tareas
tareas = new Vector();
// agrega tareas a la cola
tareas.add(new Tarea("Barrer"));
tareas.add(new Tarea("Fregar"));
tareas.add(new Tarea("Hacer la compra"));
// recupera las tareas el mismo orden que fueron
// agregadas
// mientras la cola no esté vacía...
while (! tareas.isEmpty()) {
// saca el primer elemento (el 0) de la cola...
tarea = (Tarea)tareas.remove(0);
// ... y lo muestra por pantalla
System.out.println(tarea.obtDescripción());
}
}
}


En el web de la API de Java se puede encontrar la documentación completa de la clase “Vector”*. Entre otras cosas, se explica el funcionamiento de las funciones “remove” e “isEmpty”.


Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/. 
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización SIG, Albert Gavarró Rodríguez , Universidad Oberta  de Catalunya, España 2011



Programación y Personalización SIG. Programación Orientada a Objetos

A lo largo de la historia de la informática, han ido apareciendo diferentes paradigmas de programación. En primer lugar, apareció la programación secuencial, que consistía en secuencias de sentencias que se ejecutaban una tras otra. El lenguaje ensamblador o el lenguaje COBOL son lenguajes secuenciales. Entonces no existía el concepto de función, que apareció más adelante en los lenguajes procedimentales, como el BASIC o el C. La evolución no terminó aquí, y continuó hasta el paradigma más extendido en la actualidad: la programación orientada a objetos. Smalltalk, C++ o Java pertenecen a esta nueva generación. 

Cada nuevo paradigma ha extendido el anterior, de manera que podemos encontrar las características de un lenguaje secuencial en uno procedimental, y las de uno procedimental en uno orientado a objetos. De hecho, hasta ahora sólo hemos visto la vertiente procedimental del lenguaje Java. 

Estudiaremos qué es una clase y qué es un objeto, cómo se definen y cómo se utilizan. Además, conoceremos los aspectos más relevantes de la programación orientada a objetos y las principales diferencias respecto a la programación procedimental.

2.1. Clases y objetos

En la programación orientada a objetos, aparecen por primera vez los conceptos de clase y objeto. Una clase es como una especie de patrón conceptual, mientras que un objeto es la materialización de dicho patrón. Imaginemos la clase “motocicleta”. Todos estamos de acuerdo en que todas las motocicletas tienen características comunes que nos permiten distinguirlas como tales. Una motocicleta, entre otras cosas, tiene dos ruedas, carece de techo y tiene un manillar y un motor de una determinada cilindrada. Además, será de alguna marca y tendrá algún nombre de modelo. A este esquema mental, a este patrón, lo llamaremos “clase”. Sin embargo, motocicletas hay muchas: la de mi hermano, la del vecino, la del concesionario de enfrente..., todas de diferentes marcas, cilindradas y colores. A esta materialización del patrón le daremos el nombre de “objeto”. Clase sólo hay una, pero objetos puede haber muchos. 

Las líneas siguientes declaran la clase “Producto” y sus atributos:
class Producto {
 int código; 
String descripción; 
double precio;
}

Como se puede observar, el nombre de la clase va precedido por la palabra reservada class (clase). Los atributos se declaran del mismo modo que las variables, aunque encerrados entre llaves.

Cabe recordar que una clase es siempre una simplificación de la realidad. Por esta razón, nunca declararemos atributos para todas y cada una de las características del ente que queramos representar; sólo crearemos aquellos que necesitemos para resolver el problema que se nos plantea. Por otro lado, podemos tener una o más funciones que lean y escriban esos atributos, por ejemplo, una función que se encargue de leer y otra que se encargue de cambiar (escribir) el precio del producto. Estas funciones, que están estrechamente ligadas a los atributos y que no tienen razón de ser sin ellos, se declaran en el ámbito de la clase. El código siguiente:

public class Producto {
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
}

Declara la clase “Producto” con dos funciones miembro: “fijarPrecio” y “obtenerPrecio”. Es importante observar que ambas funciones tienen visibilidad sobre los atributos de la clase, es decir, pueden acceder a ellos de la misma forma que acceden a sus propios parámetros y variables.

2.2. Instanciación de objetos
Como ya hemos comentado, una clase es sólo un patrón de objetos. Los atributos de una clase no existen en la memoria del ordenador hasta que no se materializan en un objeto. A este proceso de materialización se le llama instanciación. Un objeto se instancia mediante el operador new (nuevo). El código siguiente:  

Producto sal; sal = 
new Producto(); 

instancia el objeto “sal” de la clase “Producto”. Como se puede observar, “sal” debe declararse como una variable del tipo “Producto”. Los paréntesis después del nombre de la clase son obligatorios. A partir de la instanciación, ya tenemos en memoria un objeto con todos sus atributos. Cada nueva instanciación crea un objeto nuevo, independiente de los demás. Si tenemos, por ejemplo, dos objetos, “sal” y “azúcar”:

Producto sal, azúcar; 
sal = new Producto(); 
azúcar = new Producto(); 

en la memoria tendremos dos pares de atributos “código”, “descripción” y “precio”, uno por cada objeto:
Para acceder a estos atributos, utilizaremos el operador “.” (punto). Las líneas siguientes:

Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.precio = 0.60;
// fija el precio del paquete de azúcar
azúcar.precio = 0.81

inicializan el atributo “precio” de los objetos “sal” y “azúcar” a 0,60 y 0,81 respectivamente. Observad la independencia entre ambos objetos. El operador “.” nos permite especificar de forma unívoca a qué atributo de qué objeto hacemos referencia. De la misma forma (usando el operador “.”) accederemos a las funciones miembro de la clase. El código siguiente hace exactamente lo mismo que el anterior, pero usando la función “fijarPrecio” que anteriormente habíamos definido:

Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);


public class Producto {
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);
}
}

Las funciones miembro de una clase, como “fijarPrecio” u “obtenerPrecio”, acceden directamente a los atributos de un objeto sin necesidad de especificar a qué objeto pertenecen. Esto es así porque las funciones miembro siempre se ejecutan en el contexto de un objeto. Es decir, cuando nos encontramos con la llamada:

sal.fijarPrecio(0.60);

ejecutamos la función para el objeto “sal”. Entonces, dentro de la función, cada vez que se hace referencia a un atributo, a éste, implícitamente, se le supone de la clase “sal”.

2.3. El objeto this
Como ya hemos explicado, cuando una función miembro accede a un atributo lo hace en el contexto de un objeto. Por eso no es necesario que especifique a qué objeto hace referencia. Sin embargo, hay casos en los que puede ser interesante disponer de dicho objeto, por ejemplo para pasarlo por parámetro a otra función o para distinguirlo de una variable o un parámetro homónimo. 

Las funciones miembro pueden acceder al objeto actual mediante el objeto predefinido this (éste). A continuación, se muestra una implementación alternativa (y equivalente) de la función “fijarPrecio”:

void fijarPrecio(double precio) {
this.precio = precio;
}
En este caso, se utiliza this para distinguir entre el atributo de la clase y el parámetro de la función. El “this.precio” hace referencia al atributo “precio” del objeto actual (this), mientras que “precio”, sin el this, hace referencia al pará- metro de la función. 

En este caso, mediante this explicitamos a qué objeto hacemos referencia. Entonces, si tenemos la llamada: en el contexto de la función “fijarPrecio”, this será el mismo objeto que “sal”.  

2.4. El constructor 
Cuando se instancia un objeto, puede ser necesario inicializar sus atributos. Volviendo al ejemplo anterior, establecíamos el precio del producto después de haberlo instanciado, ya fuese accediendo directamente al atributo “precio” o mediante la función “fijarPrecio”. 

Sin embargo, las clases nos ofrecen un mecanismo mucho más elegante de inicializar un objeto: el constructor. El constructor es una función como cualquier otra, salvo por un par de particularidades: se llama como la clase y no tiene tipo de retorno. 

Las líneas siguientes muestran un posible constructor de la clase “Producto”:

Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}

El constructor espera tres parámetros: el código, la descripción y el precio del producto. En este caso, la tarea que lleva a cabo el constructor es relativamente sencilla: tan sólo copiar el código, la descripción y el precio proporcionados a los atributos homónimos. El ejemplo siguiente instancia otra vez los productos “sal” y “azúcar”. Sin embargo, esta vez se hace uso del constructor que acabamos de definir:

Producto sal, azúcar;
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(800053588, "Azúcar", 0.81);

Agrupándolo todo, el código quedaría así:  

public class Producto {
int código;
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(80005388, "Azúcar", 0.81);
System.out.println("Precio de 1 paquete de sal: " +
sal.obtenerPrecio() + " EUR");
System.out.println("Precio de 1 paquete de azúcar: " +
azúcar.obtenerPrecio() + " EUR");
}
}

Lo que sigue al operador new no es otra cosa que la llamada al constructor de la clase. Después de la instanciación, los atributos de los objetos “sal” y “azúcar” ya estarán inicializados con los valores que hayamos pasado al constructor. El programa generará la salida siguiente:

Precio de 1 paquete de sal: 0.60 EUR
Precio de 1 paquete de azúcar: 0.81 EUR

Una clase puede tener más de un constructor, del mismo modo que puede tener dos o más funciones con el mismo nombre. Sin embargo, como en el caso de las funciones, los parámetros deben ser distintos. Si no se define ningún constructor para una clase, ésta tendrá un construc- a tor implícito: el constructor por defecto. El constructor por defecto, que no espera ningún parámetro, es el que usábamos en las primeras implementaciones de la clase “Producto”, cuando aún no habíamos definido ningún constructor:

azúcar = new Producto(); // llamada al constructor por defecto 

2.5. Extensión y herencia
La programación orientada a objetos (POO de ahora en adelante) establece un mecanismo fundamental que nos permite definir una clase en términos de otra: la extensión. La idea subyacente es partir de una clase general para generar una clase más específica que considere alguna característica o funcionalidad no cubierta por la clase más general.  

Supongamos ahora que en nuestro colmado empezamos a vender productos a granel. La aproximación anterior, en la que cada producto tenía un precio, deja de ser válida para todos los productos. Ahora hay productos que tienen un precio por kilogramo y un peso, además del código y la descripción. Está claro que, en gran medida, los productos que se venden a granel no difieren demasiado de los que se venden por unidades. 

De hecho, sólo cambia un poco el significado del concepto de precio, que ahora no es por unidad sino por kilo. Además, tenemos un nuevo atributo, el peso, y una nueva forma de computar el precio, el precio (por kilo) multiplicado por el peso. Jugando con este símil, el código siguiente define la clase “ProductoGranel” como una extensión de la clase “Producto”: 

class ProductoGranel extends Producto {
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción, double
precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}

En primer lugar, observad el uso de la palabra clave extends (extiende):

class ProductoGranel extends Producto {

 Mediante esta construcción, definimos una clase en términos de otra. Esto implica que la clase “ProductoGranel” tendrá los mismos atributos y funciones que la clase “Producto”, más aquellos que defina de forma expresa. A este mecanismo de transmisión de atributos y funciones se le llama herencia. Observad también la llamada al constructor de la clase “Producto” desde el constructor de la clase “ProductoGranel” mediante la palabra reservada super (superclase):

super(código, descripción, precio);

Como ya hemos comentado, la clase “ProductoGranel” hereda los atributos y las funciones de la clase “Producto”. Esto le permite llamar al constructor de la clase “Producto” para inicializar aquellos atributos que ha heredado. La llamada al constructor de la clase madre, si la hay, debe ser la primera sentencia del constructor de la clase hija. Finalmente, podemos observar que “ProductoGranel” define una función “obtenerPrecio”, que es exactamente la misma que ya existía en la clase “Producto”. Sin embargo, su código cambia sustancialmente: ahora el precio se calcula como el producto de los atributos “precio” (por kilo) y “peso”:

double obtenerPrecio() {
 return precio * peso; 
}


La nueva función sustituirá a la heredada de “Producto”. Este mecanismo nos permite redefinir una función para que se ajuste a las características de la nueva clase. En el caso de la clase “ProductoGranel”, este cambio era necesario, pues la función “obtenerPrecio” que había heredado simplemente devolvía el valor del atributo “precio”.

El mecanismo de extensión nos permite utilizar objetos de la clase “ProductoGranel” como si fueran de la clase “Producto”. El código siguiente imprime el nombre y el precio de varios productos independientemente de su tipo: 
class Producto {
int código;
CC BY • PID_00174495 39 Introducción a la programación orientada a objetos
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
String obtenerDescripción() {
return descripción;
}
}
class ProductoGranel extends Producto {
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción,
double precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}

public class Caja {
// muestra por pantalla el precio de un producto
public static void escribirPrecio(Producto p) {
System.out.println(p.obtenerDescripción() + " " +
p.obtenerPrecio() + " EUR");
}
public static void main(String[] args) {
Producto sal;
ProductoGranel mango, salmón;
// crea los productos sal, salmón y mango
sal = new Producto(80005355, "Sal", 0.60);
salmón = new ProductoGranel(80005373, "Salmón",
9.55, 0.720);
mango = new ProductoGranel(80005312, "Mango", 2.99,
0.820);
// escribe el precio de los tres productos
escribirPrecio(sal);
escribirPrecio((Producto)salmón);
escribirPrecio((Producto)mango);
}
}

Como podemos observar, en la llamada a la función “escribirPrecio” para los objetos de la clase “ProductoGranel” hay una peculiaridad: el uso del operador de conversión:

escribirPrecio((Producto)mango) 

El operador de conversión permite cambiar el tipo de una variable a otro tipo compatible (casting, en inglés). En el ejemplo, forzamos a que “salmón” y “mango” se traten como si fuesen de la clase “Producto”. El operador de conversión toma la forma del tipo destino encerrado entre paréntesis justamente delante de la variable que deseamos convertir. El resultado será el siguiente:

Sal 0.60 EUR
Salmón 6.876 EUR
Mango 2.4518 EUR

2.6. Los paquetes y la directiva import
 
Para estructurar el código y evitar conflictos con los nombres de las clases, el lenguaje Java nos permite agrupar las clases en paquetes (packages). Normalmente, los paquetes agrupan clases que están relacionadas por alguna funcionalidad o característica. Por ejemplo, se suelen crear paquetes para agrupar las clases que implementan la interfaz de usuario de una aplicación o aquellas que proporcionan algún tipo de cálculo matemático o probabilístico.
La agrupación de clases en paquetes nos permite: • Poner de manifiesto la relación entre un conjunto de clases. 
Identificar un conjunto de clases con una funcionalidad. 
Evitar los conflictos con los nombres de las clases: puede haber clases homónimas en paquetes distintos.

Para crear un paquete, basta con escribir en la parte superior de un fichero fuente la palabra package seguida del nombre del paquete. Por ejemplo, las lí- neas siguientes: 

package postgradosig;
class Asignatura {
// (...)

definen una clase llamada “Asignatura” perteneciente al paquete “postgra- a dosig”.

Los paquetes se pueden dividir en subpaquetes de manera arbitraria, formando una jerarquía de paquetes. Las líneas siguientes:  

package edu.uoc.postgradosig;
class Asignatura {
// (...)
}

Cuando desde una clase se desea acceder a otra clase situada en otro paquete, es necesario indicarlo de forma explícita mediante la directiva import (importar). De hecho, esta directiva no se hace otra cosa que importar la definición de la clase. 

Supongamos la clase “Alumno” perteneciente al paquete “edu.uoc.postgradosig.alumno” y la clase “Asignatura” perteneciente al paquete “edu.uoc.postgradosig.asignatura”. Supongamos también que la clase “Asignatura” tiene una función “matricular” que permite matricular a un alumno a la asignatura. Entonces, el código de la clase “Asignatura” sería parecido al siguiente:

package edu.uoc.postgradosig.asignatura;
// importa la clase “Alumno”
import edu.uoc.postgradosig.alumno.Alumno;
class Asignatura {
// (...)
public void matricular(Alumno alumno) {
// (...)
}
}

2.7. Visibilidad y encapsulación
Hasta ahora hemos accedido a los atributos y las funciones de un objeto libremente, sin ninguna restricción. Sin embargo, como los atributos contienen el estado de un objeto, no suele ser deseable que código ajeno a la clase pueda acceder a ellos y modificarlos, porque, al desconocer el papel que desempeñan cada uno de los atributos en la implementación de la clase, esto podría dejar el objeto en un estado erróneo.

Se llama encapsulación a la técnica consistente en ocultar el estado de un objeto, es decir, sus atributos, de manera que sólo pueda cambiarse mediante un conjunto de funciones definidas a tal efecto. 

Los atributos y las funciones de una clase pueden incorporar en su definición un modificador que especifique la visibilidad de dicho elemento. Hay tres modificadores explícitos en Java: 

public: el elemento es público y puede accederse a él desde cualquier clase, 
protected: el elemento es semiprivado y sólo pueden acceder a él la clase que lo define y las clases que lo extienden, y 
private: el elemento es privado y sólo puede acceder a él la clase que lo define.

Si no se especifica ningún nivel de visibilidad, se aplica la regla de visibilidad implícita (conocida como package): el atributo o función será público para todas las clases del mismo paquete, y privado para las demás. A modo de ejemplo, veamos una nueva versión de la clase “Producto”: 

class Producto {
private int código;
private String descripción;
private double precio;
// el constructor: inicializa el objeto Producto
public Producto(int código, String descripción,
double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
public void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
public double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
public String obtenerDescripción() {
return descripción;
}
}

Como se puede apreciar, se han declarado todos los atributos privados y las funciones públicas. Esto implica que la sentencia

sal.precio = 0.60;

en la que “sal” es una instancia de la clase “Producto”, será inválida. En su lugar, debemos usar una llamada a la función “cambiarPrecio” creada a tal efecto:

sal.cambiarPrecio(0.60);

Es importante remarcar que al menos un constructor de la clase debe ser pú- blico. En caso contrario, no se va a poder instanciar ningún objeto de la clase.


Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/. 
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización SIG, Albert Gavarró Rodríguez , Universidad Oberta  de Catalunya, España 2011