oct 092010
 

Buenas,

Una vez visto cómo mostrar un mapa de Google Maps, cómo centrarlo y cómo poner marcas, ahora toca el momento de personalizarlo.

En la versión 3 del API de Google Maps, hay que tener en cuenta que no se permite añadir o eliminar controles de forma dinámica, éstos deben de establecerse en la creación del mapa.

El objeto MapOptions

Este objeto será el encargado de dar la forma inicial a nuestro mapa. Para ver todas sus propiedades, te aconsejo una visita a la API de Google Maps ya que aquí sólo veremos los más interesantes (o al menos desde mi punto de vista).

Obligatorias
  • center: propiedad que determina dónde se centrará el mapa al mostrarlo. Es obligatoria y es de tipo LatLng.
  • mapTypeId: propiedad de tipo MapTypeId obligatoria y que es cómo se mostrará inicialmente el mapa.
  • zoom: entero obligatorio con el zoom aplicado al mapa.
Visuales
  • disableDefaultUI: booleano para mostrar o no todos los controles del mapa. Por defecto están habilitados.
  • mapTypeControl: booleano para mostrar o no el tipo de mapa. Por defecto habilitado.
  • mapTypeControlOptions: propiedad del tipo MapTypeControlOptions para configuración del control del tipo de mapa cuando éste se muestra.
    • mapTypeIds: array con los tipos de mapa a mostrar. Ver MapTypeId.
    • position: propiedad de tipo ControlPosition para especificar dónde se mostrará el control. Por defecto TOP_RIGHT. Las constantes posibles son:
      • BOTTOM
      • BOTTOM_LEFT
      • BOTTOM_RIGHT
      • LEFT
      • RIGHT
      • TOP
      • TOP_LEFT
      • TOP_RIGHT
    • style: propiedad de tipo MapTypeControlStyle para determinar el estilo del control. Los posibles valores son:
      • DEFAULT
      • DROPDOWN_MENU
      • HORIZONTAL_BAR
  • navigationControl: booleano para mostrar o no el control de navegación.
  • navigationControlOptions: propiedad de tipo NavigationControlOptions con las propiedades del control de navegación.
    • position: propiedad de tipo ControlPosition.
    • style: propiedad de tipo NavigationControlStyle para especificar el tipo de navegador. Las constantes posibles son:
      • ANDROID
      • DEFAULT
      • SMALL
      • ZOOM_PAN
  • scaleControl: booleano para mostrar o no el control de escala.
  • scaleControlOptions: propiedad de tipo ScaleControlOptions con las opciones del control de escala.
No visuales
  • disableDoubleClickZoom: booleano para des/habilitar el zoom con el clic de ratón.
  • draggable: booleano para des/habilitar el poder mover el mapa. Por defecto se puede.
  • keyboardShortcuts: booleano para des/habilitar el control del mapa con el teclado. Por defecto habilitado.
  • scrollwheel: booleano para des/habilitar el control de la rueda del ratón. Habilitado por defecto.

Aplicando lo visto a Delphi

Una vez vistas las configuraciones posibles del mapa, nos queda ver cómo hacerlo desde Delphi.

Basándonos en los ejemplos anteriores, crearemos una constante (MAPA_CODE) que contendrá el código html y JavaScript necesario. En éste, destacar la creación de una nueva función JavaScript que será llamada desde Delphi y será la encargada de generar el mapa con la configuración que hayamos especificado en el programa. La función es esta:

  function DoWeb(Lat,Lon,TMap,aZoom,ZClick,MoveMap,Keyb,Wheel,ShowTM,
                 PosTM,StyleTM,ShowNav,PosNav,StyleNav,ShowScale,PosScale) {
    switch (TMap) {
      case "HYBRID": aTMap = google.maps.MapTypeId.HYBRID; break;
      case "ROADMAP": aTMap = google.maps.MapTypeId.ROADMAP; break;
      case "SATELLITE": aTMap = google.maps.MapTypeId.SATELLITE; break;
      default: aTMap = google.maps.MapTypeId.TERRAIN;
    } 

    switch (PosTM) {
      case "BOTTOM": aPosTM = google.maps.ControlPosition.BOTTOM; break;
      case "BOTTOM_LEFT": aPosTM = google.maps.ControlPosition.BOTTOM_LEFT; break;
      case "BOTTOM_RIGHT": aPosTM = google.maps.ControlPosition.BOTTOM_RIGHT; break;
      case "LEFT": aPosTM = google.maps.ControlPosition.LEFT; break;
      case "RIGHT": aPosTM = google.maps.ControlPosition.RIGHT; break;
      case "TOP": aPosTM = google.maps.ControlPosition.TOP; break;
      case "TOP_LEFT": aPosTM = google.maps.ControlPosition.TOP_LEFT; break;
      default: aPosTM = google.maps.ControlPosition.TOP_RIGHT;
    } 

    switch (StyleTM) {
      case "DROPDOWN_MENU": aStyleTM = google.maps.MapTypeControlStyle.DROPDOWN_MENU; break;
      case "HORIZONTAL_BAR": aStyleTM = google.maps.MapTypeControlStyle.HORIZONTAL_BAR; break;
      default: aStyleTM = google.maps.MapTypeControlStyle.DEFAULT;
    } 

    switch (PosNav) {
      case "BOTTOM": aPosNav = google.maps.ControlPosition.BOTTOM; break;
      case "BOTTOM_LEFT": aPosNav = google.maps.ControlPosition.BOTTOM_LEFT; break;
      case "BOTTOM_RIGHT": aPosNav = google.maps.ControlPosition.BOTTOM_RIGHT; break;
      case "LEFT": aPosNav = google.maps.ControlPosition.LEFT; break;
      case "RIGHT": aPosNav = google.maps.ControlPosition.RIGHT; break;
      case "TOP": aPosNav = google.maps.ControlPosition.TOP; break;
      case "TOP_LEFT": aPosNav = google.maps.ControlPosition.TOP_LEFT; break;
      default: aPosNav = google.maps.ControlPosition.TOP_RIGHT;
    } 

    switch (StyleNav) {
      case "ANDROID": aStyleNav = google.maps.NavigationControlStyle.ANDROID; break;
      case "SMALL": aStyleNav = google.maps.NavigationControlStyle.SMALL; break;
      case "ZOOM_PAN": aStyleNav = google.maps.NavigationControlStyle.ZOOM_PAN; break;
      default: aStyleNav = google.maps.NavigationControlStyle.DEFAULT;
    } 

    switch (PosScale) {
      case "BOTTOM": aPosScale = google.maps.ControlPosition.BOTTOM; break;
      case "BOTTOM_LEFT": aPosScale = google.maps.ControlPosition.BOTTOM_LEFT; break;
      case "BOTTOM_RIGHT": aPosScale = google.maps.ControlPosition.BOTTOM_RIGHT; break;
      case "LEFT": aPosScale = google.maps.ControlPosition.LEFT; break;
      case "RIGHT": aPosScale = google.maps.ControlPosition.RIGHT; break;
      case "TOP": aPosScale = google.maps.ControlPosition.TOP; break;
      case "TOP_LEFT": aPosScale = google.maps.ControlPosition.TOP_LEFT; break;
      default: aPosScale = google.maps.ControlPosition.TOP_RIGHT;
    } 

    var latlng = new google.maps.LatLng(Lat,Lon);
    var myOptions = {
      center: latlng,
      mapTypeId: aTMap,
      zoom: aZoom, 

      disableDoubleClickZoom: ZClick,
      draggable: MoveMap,
      keyboardShortcuts: Keyb,
      scrollwheel: Wheel, 

      mapTypeControl: ShowTM,
      mapTypeControlOptions: {position: aPosTM, style: aStyleTM},
      navigationControl: ShowNav,
      navigationControlOptions: {position: aPosNav, style: aStyleNav},
      scaleControl: ShowScale,
      scaleControlOptions: {position: aPosScale} 

    };
    map = null;
    map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
  }
Las constantes posibles son:

Como podemos ver, la función recibe tantos parámetros como posibles configuraciones tengamos. Lo primero que hace es coger el valor de las constantes necesarias según los parámetros recibidos (los 6 switch). Luego creamos el objeto que contendrá la longitud y latitud y el que contendrá todas las opciones del mapa. Para terminar, liberamos el objeto map por si existía y lo volvemos a crear con las características nuevas.

A nivel de código Delphi, pues queda hacer la llamada cómo ya hemos visto en anterioridad:

const
  StrParams = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s';
var
  Doc2: IHTMLDocument2;
  Win2: IHTMLWindow2;
  Params: String;
begin
  Doc2 := wbWeb.Document as IHTMLDocument2;
  Win2 := Doc2.parentWindow;

  Params := Format(StrParams, [eLong.Text,
                               eLat.Text,
                               QuotedStr(cbTypMap.Text),
                               IntToStr(tbZoom.Position),
                               LowerCase(BoolToStr(not chZoomClick.Checked, True)),
                               LowerCase(BoolToStr(chDrag.Checked, True)),
                               LowerCase(BoolToStr(chTeclado.Checked, True)),
                               LowerCase(BoolToStr(chWheel.Checked, True)),
                               LowerCase(BoolToStr(chShowTypeMap.Checked, True)),
                               QuotedStr(cbTMPosition.Text),
                               QuotedStr(cbTMStyle.Text),
                               LowerCase(BoolToStr(chNavigation.Checked, True)),
                               QuotedStr(cbNPosition.Text),
                               QuotedStr(cbNStyle.Text),
                               LowerCase(BoolToStr(chScale.Checked, True)),
                               QuotedStr(cbSPosition.Text)
                               ]);

  Win2.execScript('DoWeb(' + Params + ')', 'JavaScript');
end;

Aquí os dejo un ejemplo de la pantalla.

También podéis descargaros los fuentes y el binario del programa desde aquí.

Otro día más.

Nos leemos

oct 052010
 

Buenas,

Vimos en un mensaje anterior cómo conseguir posicionar el mapa en una determinada longitud y latitud. Ahora vamos a ver cómo poner marcas en estas longitudes y latitudes.

Para poner una marca en el mapa no es necesario tenerlo centrado dado que ésta es relativa al mapa, no al hecho del centrado. Así pues, siguiendo el ejemplo anterior, haremos que cuando se carguen las diferentes pestañas .

La clase Marker

Para crear una marca, lo que tendremos que hacer es crear un objeto de la clase Marker, así que os recomiendo que le deis un vistazo a sus métodos y eventos. Para nuestro propósito, lo que realmente nos interesa es el constructor, el cual recibe un objeto de tipo MarkerOptions con el que podremos configurar el aspecto de nuestra marca.

Este objeto tiene las siguientes propiedades:

  • clickable: booleano que nos indicará si podemos o no hacer clic en la marca.
  • cursor: string que contiene el cursor de ratón a mostrar.
  • draggable: booleano que permitirá que la marca pueda desplazarse.
  • flat: booleano que mostrará o no la sombra de la marca.
  • icon: string o MarkerImage con el icono a mostrar para la marca.
  • map: mapa asociado donde se mostrará la marca.
  • position:LatLng donde se mostrará la marca.
  • shadow: string o MarkerImage con la imagen de sombra.
  • shape: MarkerShape que indica la región dónde se podrá hacer clic o desplazar.
  • title: string con el título (hint para que nos entendamos los Delphinianos).
  • visible: booleano para hacer o no visible la marca.
  • zIndex: numérico que indicará el orden (superposición si se diera el caso) en que se mostrarán las marcas.

Cambios en el JavaScript

A nivel de JavaScript tendremos que realizar algunos cambios. Primero crearemos una función que nos genere la marca.

  function MakeMarker(Lat, Lng, titulo) {
    var latlng = new google.maps.LatLng(Lat,Lng);
    var aMarker = new google.maps.Marker({
        position: latlng,
        map: map,
        title: titulo
        });
    markerArray[markerArray.length] = aMarker;
  }

De esta función destacar el array markerArray que contendrá todas las marcas puestas en el mapa. Este array nos servirá para, en búsquedas posteriores, poder borrar las marcas antes de mostrar las nuevas. Esta variable se ha definido global como la variabla map.

Otra función que añadiremos es la de borrar las marcas

  function DeleteMarkers() {
    for (i = 0; i < markerArray.length; i++) {
      markerArray[i].setMap(null);
    }
    markerArray = [];
  }

Ahora sólo queda controlar esto desde Delphi.

Cambios en Delphi

Por lo que respecta a Delphi, crearemos un par de funciones

    procedure PonerMarca(const Long, Lat, Titulo: string);
    procedure BorrarMarcas;

Estas dos funciones se llamarán desde el evento AfterGetValues de la clase TGeoCode.

Puedes descargarte un ejemplo aquí (incluye ejecutable).

Nos leemos

sep 022010
 

Buenas,

Vimos en un mensaje anterior cómo conseguir mostrar en una aplicación Delphi un mapa de Google Maps. Ahora toca «jugar» con este mapa y, lo primero que tenemos que saber hacer es cómo centrarlo en una posición determinada (la latitud y longitud que nosotros queramos).

Para ello tendremos que realizar algún cambio en el código base de la página web. Lo primero es hacer global la variable que controla el mapa «map«. Para ello bastará con declararla fuera de la función y, por supuesto, quitar el «var» de la línea de creación del objeto google.maps.Map. Así pues, nuestro JavaScript quedará de la siguiente manera:

  var map = null;

  function initialize() {
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);
  }

Si nos damos una vuelta por la clase Map, veremos que tiene un método panTo definido de la siguiente forma

panTo(latLng:LatLng)

Este método recibe como parámetro un objeto de tipo LatLng que contendrá la longitud y latitud donde se centrará el mapa. Así pues, bastará con crear un objeto de éste tipo y hacer una llamada a este método para conseguir lo que buscamos.

Para construir el objeto de tipo LatLng y hacer la llamada al método Map.panTo, crearemos la siguiente función JavaScript en el código de la página:

  function CenterMap(Lat, Lng) {
   	map.panTo(new google.maps.LatLng(Lat, Lng));
  }

Ahora sólo quedará desde código Delphi hacer una llamada a esta función con los parámetros deseados. Para ello usaremos la interfaz IHTMLDocument2 la cual encapsula la información de la página web mostrada y la interfaz IHTMLWindow2 que contiene información sobre el objeto Windows (para más información consultar la ayuda de Delphi). La función de centrado del mapa podría ser algo así:

procedure TForm1.CentraMapa(const Long, Lat: string);
var
  Doc2: IHTMLDocument2;
  Win2: IHTMLWindow2;
  LatLng: String;
begin
  Doc2 := wbWeb.Document as IHTMLDocument2;
  Win2 := Doc2.parentWindow;

  LatLng := '"' + Lat + '", "' + Long + '"';

  Win2.execScript('CenterMap(' + LatLng + ')', 'JavaScript');
end;

Con ésto, sólo tenemos que ir llamando a nuestra función para ir moviéndonos por le mapa de Google a las coordenadas geográficas que queramos.

Puedes descargarte un ejemplo aquí (incluye ejecutable).

Continuará….

Nos leemos

sep 022010
 

Buenas,

Una vez visto cómo funcionan nuestras clases, ha llegado el momento de mostrar la información en un mapa.

Si le damos un vistazo al tutorial del API de JavaScript de Google Maps nos podemos dar cuenta que es muy sencillo el mostrar un mapa con los datos que queramos mostrar. Vasta con montar una pequeña página web, que ésta ejecute un script y mostrarla en un TWebBrowser.

La página web

Lo primero es ver y comprender qué hace esta sencilla página web que, incluso para gente que no está familiarizada con la programación web, llega a entenderlo fácilmente. Para todo el que quiera verla, les remito a dicho tutorial.

La parte qué realmente nos interesa es el JavaScript que hay. Lo primero es  incluir el API de Google mediante la línea

<script type="text/javascript"
   src="http://maps.google.com/maps/api/js?sensor=false"></script>

Recordad lo que explicamos en un artículo anterior sobre el parámetro sensor.

Y ahora se crea una función que será llamada en el evento OnLoad del tag body la cual realizará los siguientes pasos.

  • Crear un objeto para almacenar las coodenadas (latlng).
  • Crear un objeto de tipo map options que contenga todas las características del mapa
  • Crear un objeto «mapa» con los parámetros de dónde se mostrará y qué características tendrá

El resultado, éste:

<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);
  }

</script>

A nivel de características del mapa, decir que puede especificarse el zoom (zoom), la longitud y latitud en la que debe centrarse (center) y el tipo de mapa que se quiere visualizar (mapTypeId) que puede ser:

  • ROADMAP: mapa normal 2D
  • SATELLITE: muestra las fotografías de satélite
  • HYBRID: mixto entre ROADMAP y SATELLITE
  • TERRAIN: relieve físico del terreno

Esta es otra deferencia con la versión 2 del API. En la v2 existe una visión del mapa por defecto. En esta versión hay que especificarla. Si quieres saber más acerca de los tipos de mapa, te recomiendo una visita a Google Map Javascript API V3 – Map Types.

Ahora que se ha entendido lo que hace el código de la página, llega el momento de mostrarlo en nuestra aplicación Delphi. Lo primero que tendremos que decidir es dónde almacenaremos esta web, si en un archivo en disco y cargarlo en ejecución, si en un archivo de recursos o bien en una constante en el mismo código (o alguna otra manera que no se me ocurre ahora, jejeje). La del archivo físico en disco yo la descartaría de entrada por el simple motivo de que puede ser borrada por error, no copiada si se cambia de carpeta el contenido,….. Vamos, que es más fácil perder el archivo que otra cosa.

Y entre archivo de recursos o constante, pues ya depende de la manera en que nos guste programar. Si nos gusta un código más limpio, sin duda en un archivo de recursos. Pero si queremos poder ver en todo momento todo lo que afecta al programa y queremos tener un código algo más sencillo (no es que sea complicado usar un fichero de recursos), sin duda en una constante dentro del código. En este ejemplo, lo haremos mediante una constante (a la que llamaremos MAPA_CODE) en el código Delphi.

El objeto TWebBrowser

Para cargar nuestro mapa en el objeto TWebBroser dependerá de cómo lo hayamos guardado. Si lo hemos guardado como un archivo en disco, bastará con hacer una llamada al método Navigate pasándole como parámetro la ruta exacta de dónde se encuentra dicho fichero.

Y si lo que hemos hecho es almacenarlo en un fichero de recursos o en una constante, el proceder será algo diferente. La idea es conseguir tener un TStringStream que contenga el código fuente de la página web. Dado que nosotros almacenamos la página en una constante, bastará con crear una variable de éste tipo pasándole como parámetro la constante.

var
  StringStream: TStringStream;
begin
  StringStream := TStringStream.Create(MAPA_CODE);

Luego tenemos que asegurarnos que el objeto Document del TWebBrowser tiene «algo». Para ello basta con hacer una llamada a Navigate con la típica página de inicio.

    if not Assigned(wbWeb.Document) then wbWeb.Navigate('about:blank');

Ahora ya nos encontramos en la disposición de poder cargar nuestra página almacenada en el TStringStream. Para ello tenemos que coger la interfaz IPersistStreamInit del objeto Document, borrar el documento actual mediante una llamada al método InitNew del IPersistStreamInit, crear un objeto IStream con los datos del TStringStream y cargar este objeto IStream en el documento mediante el método Load del objeto IPersistStreamInit. Todo esto que parece una comida de olla, se resume en esta función:

procedure CargaWeb;
var
  StringStream: TStringStream;
  PersistStreamInit: IPersistStreamInit;
  StreamAdapter: IStream;
begin
  // creación del TStringStream con el string que contiene la página web
  StringStream := TStringStream.Create(MAPA_CODE);
  try
    // nos aseguramos de tener algún contenido en Document
    if not Assigned(wbWeb.Document) then wbWeb.Navigate('about:blank');

    // acceder a la interfaz IPersistStreamInit de Document
    if wbWeb.Document.QueryInterface(IPersistStreamInit, PersistStreamInit) = S_OK then
    begin
      // borrar datos actuales
      if PersistStreamInit.InitNew = S_OK then
      begin
        // creación y inicialización del IStream
        StreamAdapter:= TStreamAdapter.Create(StringStream);
        try
          Screen.Cursor := crHourGlass;
          // carga de la página web mediante IPersistStreamInit
          PersistStreamInit.Load(StreamAdapter);
        finally
          Screen.Cursor := crDefault;
        end;
      end;
    end;
  finally
    StringStream.Free;
  end;
end;

Si en el OnShow de un formulario hiciéramos una llamada a esta función, veríamos que en nuestro TWebBrowser se visualiza el mapa de Google en las coordenadas que trae por defecto (las que contiene nuestra constante).

Pero ésto no es lo que nosotros queremos. Lo que queremos es poder posicionar el mapa en un punto determinado y, ésto ya será parte del siguiente artículo.

Continuará….

Nos leemos

ago 312010
 

Buenas,

Vimos en un mensaje anterior cómo funcionaban las clases creadas para manejar la estructura JSON devuelta por el API de Google Maps. Ahora veremos cómo usarlas en una aplicación.

Para el ejemplo crearemos una aplicación nueva, pondremos un TEdit (eDireccion) para introducir la dirección a buscar, un TButton (bSearch) para realizar el proceso, un TMemo (mJson) para visualizar los datos recibidos – estructura JSON -, y un TValueListEditor (vlData) para visualizar el contenido del JSON de una forma más amigable.

A nivel de código, vamos a definir una variable de tipo TGeoCode en la parte privada del objeto TForm y, también en la misma sección, definiremos los manejadores de los eventos de la variable declarada que queramos manejar, en este caso los de «After»

  private
    GC: TGeoCode;

    procedure AfterGetData(Sender: TObject; AllData: string);
    procedure AfterGetValues(Sender: TObject; AllValues: TStrings);

Otra cosa que haremos es, o bien sobreescribir constructor y destructor para crear y liberar el objeto GC o bien usar los eventos OnCreate y OnDestroy del formulario. Personalmente tengo la costumbre de sobreescribir los métodos, así que vamos a ello.

Primero la declaración (en la parte pública, claro está)

  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;

Y ahora la implementación (aunque obvia para muchos)

constructor TForm1.Create(aOwner: TComponent);
begin
  inherited;

  // creación del objeto
  GC := TGeoCode.Create;
  // asignación de los eventos
  GC.AfterGetData := AfterGetData;
  GC.AfterGetValues := AfterGetValues;

  eDireccion.Text := '';
  mJson.Lines.Text := '';
end;

destructor TForm1.Destroy;
begin
  if Assigned(GC) then FreeAndNil(GC);

  inherited;
end;

Lo que haremos en los eventos no es más que rellenar los componentes que hemos puesto en pantalla. En el AfterGetData rellenaremos el memo con la estructura JSON devuelta por el API de Google Maps y en el AfterGetValues rellenaremos el TValueListEditor.

AfterGetData tiene el parámetro AllData que contiene la estructura JSON, así que será una simple asignación.

AfterGetValues tiene el parámetro AllValues de tipo TStrings que contiene una lista formateada con los diferentes valores del estilo propiedad=valor, vamos, lo que necesita cualquier TValueListEditor para ser rellenado, así que también será una sencilla asignación.

procedure TForm1.AfterGetData(Sender: TObject; AllData: string);
begin
  mJson.Lines.Text := AllData;
end;

procedure TForm1.AfterGetValues(Sender: TObject; AllValues: TStrings);
begin
  vlData.Strings.Text := AllValues.Text;
end;

Y para terminar sólo nos queda ver el código del botón de búsqueda que, por otra parte, será algo muy sencillo dado que la clase se encarga de todo

procedure TForm1.bSearchClick(Sender: TObject);
begin
  tsPlaces.Tabs.Clear;
  GC.Clear;
  GC.Direccion := eDireccion.Text;
  GC.Execute;
end;

Con esto ya tenemos el programa terminado y, el usuario podrá ver de forma clara los resultados devueltos por el API de Google Maps.

Puedes descargarte la implementación de lo que llevamos explicado aquí.

Continuará…

Nos leemos

ago 312010
 

Buenas,

Vista la estructura JSON en un mensaje anterior, ahora toca realizar el desglose de la misma en Delphi.

Por defecto, Delphi no trae nada para leer/interpretar una estructura JSON, así que tenemos dos opciones, o nos montamos nosotros algo, o miramos lo que ya hay hecho. Como no soy de los que les gusta reinventar la rueda, con una sencilla consulta a la página oficial de JSON vemos que hay varias librerías disponibles que nos realizan la labor. He escogido la JSON Delphi Library debido a que sólo es un .pas (no hay que instalar nada), trae ejemplos de su funcionamiento y su uso no es complicado.

Antes de ver el código de lectura del JSON, vamos a ver dónde lo vamos a almacenar. Para ello he diseñado un par de clases. La primera, TGeoCode, es la clase que controlará todo el resultado JSON:

  TAfterGetData = procedure (Sender: TObject; AllData: string) of object;
  TAfterGetValues = procedure (Sender: TObject; AllValues: TStrings) of object;

  TGeoCode = class
  private
    FDireccion: string;
    FGeoStatus: string;
    FGeoList: TObjectList;
    FBeforeProcess: TNotifyEvent;
    FAfterProcess: TNotifyEvent;
    FAfterGetData: TAfterGetData;
    FAfterGetValues: TAfterGetValues;
    FData: TStrings;

    function GetCount: Integer;
  protected
    procedure Parse;
    procedure ShowValues;
  public
    constructor Create(Dir: string = ''); virtual;
    destructor Destroy; override;

    // realiza la carga del JSON
    procedure Execute;
    // inicializa la estructura
    procedure Clear;

    // dirección que queremos GeoLocalizar
    property Direccion: string read FDireccion write FDireccion;
    // cantidad de GeoLocalizaciones devueltas por el API de Google Maps
    property Count: Integer read GetCount;
    // JSON devuelto por el API de Google Maps
    property Data: TStrings read FData write FData;
    // *********** propiedades devueltas por la geolocalización ***************
    // estado de la consulta
    property GeoStatus: string read FGeoStatus write FGeoStatus;
    // lista de objetos de la clase TPlace, es decir, cada una de las GeoLocalizaciones
    //    devueltar en el JSON
    property GeoList: TObjectList read FGeoList write FGeoList;
    // eventos
    property BeforeProcess: TNotifyEvent read FBeforeProcess write FBeforeProcess;
    property AfterProcess: TNotifyEvent read FAfterProcess write FAfterProcess;
    property AfterGetData: TAfterGetData read FAfterGetData write FAfterGetData;
    property AfterGetValues: TAfterGetValues read FAfterGetValues write FAfterGetValues;
  end;

Como vemos, también le he definido 4 eventos, para así poder, en el caso que se quiera, alguna pantalla de espera, cambiar el ratón para decir que se está trabajando, leer la info extraída justo después de que se haga,….. Vamos, para hacer lo que uno buenamente quiera.

La segunda clase es TPlace que contendrá cada una de las GeoLocalizaciones devueltas en la estructura JSON.

  TGeoGeometry = record
    Lat: Real;
    Lon: Real;
  end;

  TAddrComp = record
    LongName: string;
    ShortName: string;
    AddrCompType: TStrings;
  end;

  TGeoAddrComp = array of TAddrComp;

  TPlace = class
  private
    FGeoAddrComp: TGeoAddrComp;
    FGeoFormatedAddr: string;
    FGeoType: TStrings;
    FGeoGeometry: TGeoGeometry;
  public
    constructor Create; virtual;
    destructor Destroy; override;

    property GeoType: TStrings read FGeoType write FGeoType;
    property GeoFormatedAddr: string read FGeoFormatedAddr write FGeoFormatedAddr;
    property GeoAddrComp: TGeoAddrComp read FGeoAddrComp write FGeoAddrComp;
    property GeoGeometry: TGeoGeometry read FGeoGeometry write FGeoGeometry;
  end;

Ahora ya sólo queda ver cómo se cargaría la estructura JSON en objetos de nuestra clase, o lo que es lo mismo, ver el método Execute de la clase TGeoCode.

Lo primero que tenemos que hacer es crear un objeto de la clase TlkJSONobject el cual se encargará de leer la estructura JSON y montar su estructura interna. Luego sólo tendremos que recorrer esta estructura para ir almacenándola en nuestros objetos.

procedure TGeoCode.Execute;
var
  IdHTTP: TIdHTTP;
  Str: string;
  Stream: TStringStream;
begin
  if FDireccion = '' then Exit;

  if Assigned(FBeforeProcess) then FBeforeProcess(Self);

  IdHTTP := TIdHTTP.Create(nil);
  Stream := TStringStream.Create('');
  try
    // sustituimos blancos por signo +
    Str := AnsiReplaceStr(FDireccion, CHAR_SPACE, CHAR_PLUS);
    // generamos url con parámetros
    Str := STR_WEB + Str + STR_SENSOR + STR_REGION;
    // hacemos petición a la API de Google
    IdHTTP.Get(Str, Stream);
    // pasamos de UTF8 a string
    FData.Text := UTF8ToString(Stream.DataString);
    if Assigned(FAfterGetData) then FAfterGetData(Self, FData.Text);
    // realizamos PARSE
    Parse;
    // mostramos valores en la lista
    ShowValues;
  finally
    if Assigned(Stream) then FreeAndNil(Stream);
    if Assigned(IdHTTP) then FreeAndNil(IdHTTP);
  end;

  if Assigned(FAfterProcess) then FAfterProcess(Self);
end;

En el próximo capítulo veremos cómo funcionan nuestras clases en una aplicación para mostrar los datos recibidos en, por ejemplo, un TValueListEditor.

Puedes descargarte la unit GeoCode desde aquí.

continuará……..

Nos leemos

ago 312010
 

Buenas,

Vimos en un mensaje anterior qué era y cómo obtener las coordenadas geográficas de una determinada dirección mediante la API de GeoCodificación de Google Maps. También vimos que la API podía devolver el resultado en dos formatos diferentes, JSON y XML. Me he decidido a explicar el JSON dado que es lo recomendado por Google. Para quien no sepa qué es el JSON, les recomiendo una visita a su web o en la wiki.

La estructura JSON

Cuando realizamos una búsqueda, ésta puede contener una dirección exacta de lo que buscamos (ej: Plaza de España, 5, Barcelona, España) o bien una parte de la misma (Ej: Plaza de España, 5). Como bien nos podemos imaginar, de «Plaza de España» hay muchas, por lo que la API de Google Maps nos devolverá todas las coincidencias que encuentre hasta un máximo de 10 (esta cantidad no la he visto reflejada en la documentación de Google Maps pero por las pruebas que he realizado parece que es así, no obstante, ésto no quiere decir que no salga dicha cantidad en la documentación).

Así pues, la estructura JSON devuelta por el API tendrá una parte común a todos los resultados (el estado de la consulta) y un array que contendrá todos los resultados.

Ésta sería una estructura base para 1 resultado de la llamada a la API:

{
  "status": "OK",
  "results": [ {
    "types": [ "street_address" ],
    "formatted_address": "Plaça d'Espanya, 5, 08014 Barcelona, Spain",
    "address_components": [ {
      "long_name": "5",
      "short_name": "5",
      "types": [ "street_number" ]
    }, {
      "long_name": "Plaça d'Espanya",
      "short_name": "Plaça d'Espanya",
      "types": [ "route" ]
    }, {
      "long_name": "Barcelona",
      "short_name": "Barcelona",
      "types": [ "locality", "political" ]
    }, {
      "long_name": "Barcelona",
      "short_name": "B",
      "types": [ "administrative_area_level_2", "political" ]
    }, {
      "long_name": "Catalonia",
      "short_name": "CT",
      "types": [ "administrative_area_level_1", "political" ]
    }, {
      "long_name": "Spain",
      "short_name": "ES",
      "types": [ "country", "political" ]
    }, {
      "long_name": "08014",
      "short_name": "08014",
      "types": [ "postal_code" ]
    } ],
    "geometry": {
      "location": {
        "lat": 41.3749536,
        "lng": 2.1485485
      },
      "location_type": "RANGE_INTERPOLATED",
      "viewport": {
        "southwest": {
          "lat": 41.3718069,
          "lng": 2.1454125
        },
        "northeast": {
          "lat": 41.3781022,
          "lng": 2.1517078
        }
      },
      "bounds": {
        "southwest": {
          "lat": 41.3749536,
          "lng": 2.1485485
        },
        "northeast": {
          "lat": 41.3749555,
          "lng": 2.1485718
        }
      }
    }
  } ]
}

Veamos cada una de las partes de este resultado:

  • status: estado de la consulta. Hay varios estados, ver la API para consultarlos. A nosotros nos interesa el estado «OK» devuelto cuando la consulta se realiza correctamente y sin errores.
  • results: array con los resultados devueltos por el API. Si no hay resultados a mostrar, devolverá un array vacío.
    • types: array con uno o más tags indicando el tipo de resultado. Ver en el API los diferentes tipos posibles.
    • formatted_address: dirección en formato «legible» devuelta por el API de Google Maps
    • address_components: array que contiene de forma desglosada el contenido de formatted_address. Éste suele tener la siguiente información:
      • types: array con el tipo de componente
      • long_name: todo el texto descriptivo del componente
      • short_name: abreviatura del componente si existiera.
    • geometry: lista con datos de localización
      • location: lista que contiene las coordenadas geográficas
        • lat: latitud
        • lng: longitud
      • location_type: información extra sobre la localización. Ver en el API los diferentes valores.
      • viewport: lista que contiene la ventana recomendada para la visualización del resultado.
        • southwest: lista con las coordenadas suroeste de la ventana.
          • lat: latitud
          • lng: longitud
        • northeast: lista con las coordenada noreste de la ventana.
          • lat: latitud
          • lng: longitud
      • bounds: (opcional) lista con la ventana que contiene la totalidad del resultado. Destacar que no tiene porque coincidir con el viewport.
        • southwest: lista con las coordenadas suroeste de la ventana.
          • lat: latitud
          • lng: longitud
        • northeast: lista con las coordenada noreste de la ventana.
          • lat: latitud
          • lng: longitud
    • partial_match: indica la exactitud del resultado devuelto. Aunque la documentación del API de Google Maps no lo marca como opcional, en las pruebas que he realizado no siempre ha devuelto este parámetro.

Continuará…

Nos leemos

ago 312010
 

Buenas,

Pasando el rato por internet, yendo de página en página para pasar el rato, me topé con el blog de un viejo conocido, Neftalí. En él vi que, en cuatro muy buenos mensajes, explicaba cómo se consultaba y mostraba en un programa Delphi información de Google Maps.

La verdad es que me gustó mucho el tema y nunca se me hubiera pasado por la cabeza el usar Google Maps en una aplicación Delphi. Desde aquí le doy las gracias por abrirme los ojos, jejeje

Interesado en el tema, empecé a investigarlo y me di cuenta que lo explicado por Neftalí (y otras webs consultadas) nos mostraban el uso de la v2 de la API de Google Maps la cual, actualmente está marcada en desuso. Así que decidí dedicar algo de tiempo a actualizar estas demos encontradas a la nueva versión de la API, la v3.

La GeoCodificación

Como la propia web del API de Google Maps dice, «la GeoCodificación es el proceso de convertir una dirección (como «1600 Amphitheatre Parkway, Mountain View, CA») en coordenadas geográficas (como latitud 37.423021 y longitud -122.083739), que pueden usarse para poner marcas o posiciones en el mapa«.

Aunque podemos encontrar toda la documentación sobre la API de GeoCodificación aquí y su uso no sea demasiado complejo, vamos a explicar cómo funciona.

La idea es sencilla: se realiza una llamada a una determinada URL con una serie de parámetros y Google Maps nos devuelve un resultado formateado como le hayamos indicado.

Aquí tenemos la primera diferencia entre la V2 y la V3. En la primera, el formato devuelto era un parámetro más de la llamada y tenía 4 formatos posibles. En cambio, en la V3 el formato no es una parámetro y sólo tiene dos posibles salidas, o JSON o XML.

La url a la que se tiene que realizar la llamada es la siguiente:
http://maps.google.com/maps/api/geocode/output?parameters

Donde output define el formato de salida pudiendo ser json o xml quedando url como estas:
http://maps.google.com/maps/api/geocode/json?parameters
http://maps.google.com/maps/api/geocode/xml?parameters

Y los parámetros son los siguientes:

  • address o latlng: obligatorio. Es la dirección que queremos GeoCodificar (no puede contener espacios, hay que sustituirlos por un «+») o bien las coordenadas geográficas directamente. Ej: ?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA
  • sensor: obligatorio. Indica si el dispositivo desde el que se hace la petición dispone de sensor de localización. Ej: &sensor=false
  • language: opcional. Determina el idioma de salida. Si no se indica, se intentará usar el idioma del dominio que realiza la petición (si es posible), sino se tomará el inglés. Ej: &language=es. Lista de idiomas soportados.
  • region: opcional. Código de la región en formato ccTLD (country code top-level domain). La mayoría de códigos ccTLD corresponden a los códigos ISO 3166-1, aunque hay exepciones. Ej: &region=es
  • bounds: opcional. Especifica una zona geográfica en la que buscar de forma preferida. Se realiza mediante la definición de un cuadro especificando sus lados noreste y suroeste separados por la barra vertical (|). Ej: &bounds=34.172684,-118.604794|34.236144,-118.500938

Una vez explicada la teoría, el implementar ésto en Delphi no resulta complicado. Bastará con tener un TEdit donde introducir la dirección a buscar, un TButton donde pondremos el código de la llamada, un TIdHTTP con el que realizar la petición web y un TMemo en el que mostrar el resultado obtenido. El código del TButton debería de ser algo así:

const
  CHAR_SPACE = ' ';
  CHAR_PLUS = '+';
  STR_WEB = 'http://maps.google.com/maps/api/geocode/json?address=';
  STR_SENSOR = '&sensor=false';
  STR_REGION = '&region=es';
var
  Str: string;
  Stream: TStringStream;
begin
  Stream := TStringStream.Create('');
  try
    // sustituimos blancos por signo +
    Str := AnsiReplaceStr(Edit1.Text, CHAR_SPACE, CHAR_PLUS);
    // generamos url con parámetros
    Str := STR_WEB + Str + STR_SENSOR + STR_REGION;
    // hacemos petición a la API de Google
    IdHTTP.Get(Str, Stream);
    // pasamos de UTF8 a string
    Memo1.Lines.Text := UTF8ToString(Stream.DataString);
  finally
    if Assigned(Stream) then FreeAndNil(Stream);
  end;
end;

No olvidemos de incluir en la cláusula uses la unit StrUtils.

Continuará……

Nos leemos