En esta segunda entrada de esta serie vamos a ver cómo descargar nuestra primera imagen (SQL Server) y cómo crear un contenedor a partir de ella. Partimos de la entrada anterior donde al acabar ya teníamos instalada nuestra aplicación «Docker Desktop», preparada para empezar.
Os dejo aquí la lista de links a las entradas de la serie, que iré completando a medida que vayan saliendo…
Como he comentado antes, yo voy a utilizar Docker para virtualizar un SQLServer en varias de sus versiones (2107, 2019 y 2022), de esa forma podré «arrancar» un contenedor con cualquiera de estas versiones para probar mi aplicación con ellas.
Esto me evitará tener en mi máquina instaladas estas tres versiones, con el consiguiente espacio de disco ocupado y la consiguiente memoria ocupada (por ejemplo, por los servicios de SQL Server). En general, de esta forma (utilizando contenedores) conseguimos tener nuestra máquina más «limpia».
Para descargar nuestra imagen accederemos a la ventana de Terminal desde Docker y escribiremos el siguiente comando (pedimos la última imagen disponible de mssql 2017):
Una vez finalizada la descarga deberíamos tener disponible nuestra imagen descargada. Con los datos de tamaño y un pequeño menú a la derecha con algunas opciones básicas.
Todavía no podemos trabajar con nuestro SQLServer, para poder hacerlo necesitamos crear, al menos, un contenedor/instancia a partir de la imagen descargada.
Crear un contenedor a partir de una imagen
Para crear un contenedor o instancia de una imagen y trabajar con ella, necesitamos (o podemos hacerlo) utilizando el comando run. Vamos a ver el comando desde la pantalla de Terminal o la opción visual que nos ofrece Docker.
ACCEPT_EULA: Nos permite confirmar la licencia de usuario del producto (usaremos el valor Y -yes-).
MSSQL_SA_PASSWORD: Nos permite definir el password para el usuario sa (lo usaremos luego para conectarnos).
MSSQL_PID: Nos permite «modificar» la versión con la que se creará el contenedor a partir de la imagen (Evaluation, Developer, Express, Web, Standard, Enterprise (*), EnterpriseCore (*), A product key).
Podemos crear y arrancar nuestro contenedor desde la pantalla de Terminal utilizando el siguiente comando (run); Y posteriormente he lanzado «Docker ps» para ver los contenedores en marcha y en la imagen inferior véis la respuesta:
Ahí podéis ver las variables de entorno SA_PASSWORD, ACCEPT_EULA, MSSQL_PID y los valores que les he asignado.
Si queremos hacer lo mismo desde Docker Desktop, basta con pulsar el botón de RUN que hay junto a la imagen y rellenar los parámetros (variables de entorno) en la pantalla que aparece.
El resultado en ambos casos debería ser el mismo, si nos vamos a la sección de [Containers] de la aplicación deberíamos ver nuestro nuevo contenedor de SQL Server 2017 versión Express, en marcha.
Voy además a arrancar una segunda instancia, en este caso con la versión «Developer«; Necesito cambiar el puerto para que no colisionen y además le voy a poner un nombre diferente; Para ello utilizaré el siguiente comando:
NOTA: Cuando he escrito el artículo he generado las 2 instancias en Inglés, pero posteriormente realizando otras pruebas he añadido el parámetro MSSQL_LCID con el valor 3082 y con eso se consigue tener el servidor en español.
-e MSSQL_LCID=3082
Conectar a las instancias de SQL Server
Sólo nos queda conectar a las 2 instancias (contenedores) de SQL Server que hemos arrancado, como si estuvieran instalador en nuestro equipo. Para ello voy a hacerlo desde el «Management Studio» como lo hago habitualmente para conectarme a otros servidores y al mio en local.
Utilizaré los siguientes parámetros para conectarme a ambas instancias; Estos para la primera:
.
Y los mismos, pero cambiando el puerto de conexión para la segunda:
.
Ahora que ambas están conectadas podemos ver las propiedades y ver que ambos coinciden con las configuraciones que hemos definido al arrancar nuestros contenedores. Los puertos son el 1433 y el 14333 y las versiones son la Express y la Developer del SQL Server 2017 (versión interna 14.0).
Ahora ya podemos conectar nuestra aplicación a cualquiera de nuestras instancias.
Hasta aquí esta segunda entrada. Espero que os haya sido útil. Como siempre los comentarios, sugerencias y críticas son bienvenidas.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Continuando con las entradas anteriores, en esta última vamos a crear una extensión para visualizar los tipos de imágenes más habituales y usados en Windows. Además vamos a «redistribuir» un poco nuestro esqueleto inicial para reorganizar las clases (tal y como comentamos en entradas anteriores); Y finalmente vamos a crear un instalador para poder ofrecer esta extensión de una forma más profesional.
Os adjunto los links de todas las entradas de esta serie, como he hecho en las anteriores:
Con todo los visto hasta ahora en las entradas anteriores, vamos a crear una DLL para imágenes de diferentes formatos, modificando un poco el esqueleto del proyecto que teníamos hasta ahora.
Vamos a tratar las extensiones más comunes con las que trabajamos: bmp, jpg, jpeg, png, gif, ico, wmf, emf, tif, tiff
Además vamos a generar un instalador con Inno Setup que instale (y desinstale cuando sea necesario) nuestra DLL de previsualización. Inno Setup, para los que no lo conozcáis, es un generador de instalaciones para Windows muy potente y totalmente gratuito. En otras ocasiones ya he hablado de él, y a día de hoy es un estándar en generar programas de instalación.
Además de ser sencillo de utilizar, hay multitud de scripts predefinidos y de ejemplo por la web, que podéis utilizar, modificar y adaptar a vuestras necesidades. Para lo que necesitamos nosotros veréis que es sumamente sencillo y fácil.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Muchas veces nos encontramos en nuestros programas con que necesitamos imágenes o iconos para ilustrar acciones o elementos de nuestros menús o barras de botones. Personalmente utilizo alguno que tengo como «librería habitual»; Pero otras veces necesitamos algo diferente o las características del proyecto obligan a buscar algo nuevo.
Hay muchas librerías profesionales de iconos (de muy alta calidad), pero también hay lugares donde se pueden conseguir infinidad de iconos de forma gratuita.
He aquí un pequeño repaso de los que yo visito con mayor asiduidad.
(29/06/2015) Actualizado; He eliminado algunas webs que ya no están disponibles del listado anterior y he añaduido también algunas nuevas.
Un poco lenta a aveces, pero posee gran cantidad de iconos disponibles. Una vez seleccionada una palabra para la búsqueda, posee selectores para afinar los resultados en cuanto a tamño, color (imprtante),…
Esta web en un poco diferente al resto, puesto que tal y como indica su nombre es una especie de utilidad para generar iconos personalizados (dentro de unos límites). Nos solicita un tamaño, un color, un icono «base» y un pequeño icono «badge» para la zona inderior derecha (como la flecha de un acceso directo). A partir de todo esto, ‘podemos descargar el icono generado.
Como su nombre indica, es una web enfocada a iconos con estilo «plano» que están utilizandose más últimamente con las inteficies de las nuevas versiones de Windows. Faltan selectores por categorías o tamaños como tienen otras webs, pero es un buen lugar donde buscar si necesiotamos iconos con la característica comentada (flat).
Muy buena interface una vez realizada la búsqueda. La selección de tamaños, la visualización en pantalla,… se puede realizar una vez que se muestran los resultados y es muy ágil. La visualización de los iconos una vez seleccionados en muy buena y mustra diferentes tamaños del mismo icono junto al resultado.
Muestra menos resultados que los anteriores. La información cuando muestra el icono seleccionado es buena y prioriza mostrar iconos del mismo «icon-set» que el seleccionado.
_____________________________________________________________
Personalmente encuentro que devuelve menos resultados que los primeros buscadores que he indicado. No es de los que tiene mejor interface, pero cumple bien su función.
Uno de los últimos que he descubierto. Me resulta demasiado recargado el entorno y la búsqueda de iconos. La página está muy «espesa y el número de iconos que devuelve no está entre las páginas con más resultados retorna.
_____________________________________________________________
Esta también se encuentra entre las páginas que devuelve más resultados. La página de respuesta es bastante limpia y permite algun filtro (por tamaño) una vez que los resultados ya se han mostrado. No me gusta mucho que los resultados salgan mezclados en cuanto al tamaño de las imágenes, pero como ya he dcho es fácil filtrarlas una vez que estamos en los resultados.
Una vez seleccionado el icono la pantalla es clara y de las más cuidadas. También está entre mis preferidas.
_____________________________________________________________
Es de las últimas que he descubierto y me da la sensación de que es de las más nuevas. No es de las que más resultados devuelve y la forma de mostrarlos tampoco es de las que más me agrade. Mezcla los resultados por tamaños y no permite filtrar entre ellos de forma fácil. La página de resultados es liosa, pero en cambio una vez seleccionado el icono la página que se muestra es más clara y hay acceso rápido a iconos relacionados (por tamaño y por «icon-set»).
_____________________________________________________________
Esta se encuentra también entre las últimas que he descubierto. No ses de las que más resultados muestra (de las que menos en realidad), pero posee los mejores filtros una vez que se accede a los resultados; La página está muy cuidada y la información se muestra de una forma muy clara.
_____________________________________________________________
También es de los últimos que he encontrado, y aunque no es de los que devuelve más iconos, los filtros a posteriori una vez completada la búsqueda son los mejores junto con los de IconArchive. La página que muestra el icono seleccionado también es muy clara y da mucha información.
_____________________________________________________________
Es web es algo diferente al resto, pero la he incluído aquí porque en algun caso también me ha servido. Se trata de una serie de 112 iconos (generales) que se pueden personalizar para obtener muchas combinaciones. A partir de los iconos base se puede seleccionar el tamaño, el color (tonalidad general del icono) y el fondo, de forma que se obtienen muchas combinaciones a partir de los 112 originales.
_____________________________________________________________
Una vez encontrados los iconos, hay que mostrar especial atención a la licencia de cada uno de ellos, porque hay variantes.
Recomiendo que si usáis las páginas no es quedéis con una (tal vez tampoco con todas), pero realizar una búsqueda en 3 o 4 de ellas y revisar los resultados combinados suele dar muy buenos resultados (al menos eso es lo que hago yo ;-) ).
Espero que os se a útil.
AÑADO: Cualquier enlace que no se encuentre en la lista y que consideréis interesante será bienvenido.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Haciendo pruebas con el componente TidHTTP me ha surgido este ejemplo y me parece interesante mostrar el proceso completo que he seguido para descargar contenido desde una web. En este caso he accedido a las imágenes en formato PNG, pero es extensible a cualquier otros contenidos.
En un primer paso se trata de obtener el contenido (source) desde una página web y a partir de ahí, extraer los links (en mi caso correspondientes a imágenes en formato PNG) y descargar los ficheros para almacenarlos en disco. Para ello utilizaremos el control TidHTTP de la Indy.
Para obtener el código fuente de la página podemos utilizar un código como este:
// URL de la página
StrURL :=...;// Crear componente para acceder
IdH := TIdHTTP.Create(nil);// proteccion para liberartry// proteccion por errortry// Obtenemos la pagina
Resp := IdH.Get(Trim(StrURL));// recuperamos la respuesta
TSContent.Text:= Resp;excepton E:Exception dobegin
MessageDlg('Error al acceder a la página. '+#13+#10+'Mensaje: '+
E.Message, mtError,[mbOK],0);end;end;// tryfinally
IdH.Free;end;
// URL de la página
StrURL := ... ;
// Crear componente para acceder
IdH := TIdHTTP.Create(nil);
// proteccion para liberar
try
// proteccion por error
try
// Obtenemos la pagina
Resp := IdH.Get(Trim(StrURL));
// recuperamos la respuesta
TSContent.Text := Resp;
except
on E:Exception do begin
MessageDlg('Error al acceder a la página. '+#13+#10+
'Mensaje: ' +
E.Message, mtError, [mbOK], 0);
end;
end; // try
finally
IdH.Free;
end;
Obtendremos como resultado del Get el siguiente texto, que corresponde al código fuente de la página:
Lo siguiente que vamos a hacer es un simple «parseo» buscando los enlaces que nos interesen. En nuestro ejemplo queremos links a imágenes en formato png. Utilizando funciones de la clase SysUtils podemos obtener sin mayor problemas los links contenidos en el código; Si probáis con la página de ejemplo, deberíais obtener un único link:
Por último, nos queda ver el código necesario para descargar la imagen y almacenarla en disco. Para ello podemos utilizar de nuevo el componente TidHTTP. En mi caso, y en previsión de que pueda haber varias imágenes a descargar, he creado una clase derivada de TThread para ello:
{: Clase para descargar una imagen y almacenarla en disco.}
TDownImageThread =class(TThread)private
FURLImage:string;
FPathImage:string;
FFileNameImage:string;// Internas
ImageName:string;
PathURL:string;// Componente
idH:TidHTTP;public// redefinir métodosconstructor Create(AURL:string; AOutPathImages:string);destructor Destroy;override;procedure Execute;override;{: URL de la imagen a descargar. }property URLImage:stringread FURLImage write FURLImage;{: Path de disco local donde voy a almacenar la imagen.}property PathImage:stringread FPathImage;{: Nombre completa (path+Nombre) de la imagen almacenada en disco local}property FileNameImage:stringread FFileNameImage;end;
{: Clase para descargar una imagen y almacenarla en disco.}
TDownImageThread = class(TThread)
private
FURLImage: string;
FPathImage: string;
FFileNameImage: string;
// Internas
ImageName: string;
PathURL: string;
// Componente
idH:TidHTTP;
public
// redefinir métodos
constructor Create(AURL:string; AOutPathImages:string);
destructor Destroy; override;
procedure Execute; override;
{: URL de la imagen a descargar. }
property URLImage:string read FURLImage write FURLImage;
{: Path de disco local donde voy a almacenar la imagen.}
property PathImage:string read FPathImage;
{: Nombre completa (path+Nombre) de la imagen almacenada en disco local}
property FileNameImage:string read FFileNameImage;
end;
Al crear el Thread (método Create) ya pasamos como parámetros, la URL de la imagen a descargar y el Directorio inicial donde se van a guardar las imágenes encontradas.
El método Execute creamos un componente TidHTTP (igual a como lo hemos hecho antes), pero en este caso utilizamos un TFileStream para recoger el fichero que descargamos y almacenarlo en disco.
Utilizamos el Path de la URL para generar diferentes directorios para las diferentes imágenes:
//: recupara la imagen y la guarda en discoprocedure TDownImageThread.Execute();var
Stream:TFileStream;
IdH:TidHTTP;
path:string;
dir:string;begin// Directorio de salida
dir := AnsiReplaceText(PathURL,'/', STR_EMPTY);// Nombre vacíoif(ImageName = STR_EMPTY)thenbegin
Exit;end;// Path de salida
path :=IncludeTrailingBackslash(IncludeTrailingBackslash(PathImage)+ dir)+ ImageName;// Crearlo por si no existeForceDirectories(ExtractFilePath(path));try// Stream para la imagfen
Stream := TFileStream.Create(path, fmCreate);try//recuperar la imagen
IdH := TidHTTP.Create(nil);
IdH.AllowCookies:=True;// protecciontry
IdH.Get(Trim( FURLImage), Stream);except// Error al descargar la imagen//.. Volcarlo al logend;finally// Liberar
idH.Free;
Stream.Free;end;// Path de salida
FFileNameImage := path;except// error al crear el fichero//... Logend;end;
//: recupara la imagen y la guarda en disco
procedure TDownImageThread.Execute();
var
Stream:TFileStream;
IdH:TidHTTP;
path:string;
dir:string;
begin
// Directorio de salida
dir := AnsiReplaceText(PathURL, '/', STR_EMPTY);
// Nombre vacío
if (ImageName = STR_EMPTY) then begin
Exit;
end;
// Path de salida
path := IncludeTrailingBackslash(IncludeTrailingBackslash(PathImage)
+ dir) + ImageName;
// Crearlo por si no existe
ForceDirectories(ExtractFilePath(path));
try
// Stream para la imagfen
Stream := TFileStream.Create(path, fmCreate);
try
//recuperar la imagen
IdH := TidHTTP.Create(nil);
IdH.AllowCookies := True;
// proteccion
try
IdH.Get(Trim( FURLImage), Stream);
except
// Error al descargar la imagen
//.. Volcarlo al log
end;
finally
// Liberar
idH.Free;
Stream.Free;
end;
// Path de salida
FFileNameImage := path;
except
// error al crear el fichero
//... Log
end;
end;
El resultado se puede ver en este ejemplo.
Una vez descargadas las imágenes se muestran en un componente en la misma aplicación, y desde ahí podemos acceder a la información de la imagen y realizar algunas acciones sobre ellas.
Para realizar pruebas podéis introducir, por ejemplo direcciones como:
Por lo tanto para poder descargar tanto la página inicial (de donde se extraen los enlaces), como las imágenes en si, hay que modificar ligeramente el programa.
Hay que utilizar el componente TIdSSLIOHandlerSocket para poder utilizar SSL y añadir al proyecto as librerías de acceso a SSL.
No hay muchas modificaciones en el código, pero si queréis verlas, dejaré el código antiguo y el nuevo (compilado en Delphi 7) para que podáis compararlos.
NOTA: Para generar las imágenes en disco PNG he utilizado el componente “Portable Network Graphics Delphi” de Gustavo Huffenbacher Daud, que podéis encontrar y descargar de forma gratuita de Internet.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Puede ser algo trivial para los que llevamos un tiempo en esto, pero no tanto para los que empiezan; Reconozco que yo al principio también me liaba con los Canvas, los Rect, los ClientRect y no sabía cual copiar en cada cual. ;-)
El código para unir cuatro imágenes en una sóla formando un cuadro, es muy sencillo y sería algo así:
var
ARect:TRect;
path:string;begin// Añadir los 4 canvas a la nueva imagen
ARect.Left:=0; ARect.Top:=0; ARect.Right:=48; ARect.Bottom:=48;
imgDestino.Canvas.CopyRect(ARect, img1.Canvas, img1.ClientRect);
ARect.Left:=48; ARect.Top:=0; ARect.Right:=92; ARect.Bottom:=48;
imgDestino.Canvas.CopyRect(ARect, img2.Canvas, img2.ClientRect);
ARect.Left:=0; ARect.Top:=48; ARect.Right:=48; ARect.Bottom:=92;
imgDestino.Canvas.CopyRect(ARect, img3.Canvas, img3.ClientRect);
ARect.Left:=48; ARect.Top:=48; ARect.Right:=92; ARect.Bottom:=92;
imgDestino.Canvas.CopyRect(ARect, img4.Canvas, img4.ClientRect);// Path para grabarla a disco
path :=ChangeFileExt(Application.ExeName,'.bmp');
imgDestino.Picture.SaveToFile(path);
MessageDlg('Se ha guardado la imagen con el nombre: '+
path, mtInformation,[mbOK],0);
var
ARect:TRect;
path:string;
begin
// Añadir los 4 canvas a la nueva imagen
ARect.Left := 0; ARect.Top := 0; ARect.Right := 48; ARect.Bottom := 48;
imgDestino.Canvas.CopyRect(ARect, img1.Canvas, img1.ClientRect);
ARect.Left := 48; ARect.Top := 0; ARect.Right := 92; ARect.Bottom := 48;
imgDestino.Canvas.CopyRect(ARect, img2.Canvas, img2.ClientRect);
ARect.Left := 0; ARect.Top := 48; ARect.Right := 48; ARect.Bottom := 92;
imgDestino.Canvas.CopyRect(ARect, img3.Canvas, img3.ClientRect);
ARect.Left := 48; ARect.Top := 48; ARect.Right := 92; ARect.Bottom := 92;
imgDestino.Canvas.CopyRect(ARect, img4.Canvas, img4.ClientRect);
// Path para grabarla a disco
path := ChangeFileExt(Application.ExeName, '.bmp');
imgDestino.Picture.SaveToFile(path);
MessageDlg('Se ha guardado la imagen con el nombre: ' +
path, mtInformation, [mbOK], 0);
Si en lugar de unirlas formando un cuadro, se quieren unir de otra forma (las 4 en línea, por ejemplo), basta con cambiar las coordenadas de destino.
Si hubiera que cambiar el tamaño final, por ejemplo para reducirlo hasta el de las imágenes originales, se podrían aplicar técnicas de antialiasing al redimensionar.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
En la última entrada acerca de la API de Google Maps, vimos cómo mostrar un Mapa utilizando la API en un programa Delphi, cómo centrarlo en una posición predefinida y utilizando un Zoom predeterminado.
Además como seleccionar entre los diferentes controles que podemos añadir al mapa (Zoom, Vista general, tipo de mapa…).
También cómo colocar una marca en una ubicación y cómo generar una ventana con información asociada a esa marca (NOTA1).
Una vez visto cómo mostrar el mapa, lo que nos queda es ver cómo podemos, desde nuestro programa Delphi, interactuar con él; Es decir, que el usuario pueda modificar determinadas características del mapa que tiene en pantalla, y nosotros podamos recuperar esos cambios, para utilizarlos en nuestro programa.
Vamos a continuar con el ejemplo visto en las entradas anteriores; Mostraremos al usuario un Mapa ubicado en una determinada posición. El usuario debe poder «reubicar» la vista del mapa y modificar el Zoom con que está visualizando el mapa, y esos son los valores que obtendremos para posteriormente almacenarlos y actualizarlos en nuestro programa.
Coordenada de Longitud
Coordenada de Latitud
Zoom actual del mapa
La forma de conseguirlo, es añadir a la página web el código necesario para capturar eventos que se produzcan en el mapa. Incluiremos controles de edición, donde se almacenan la longitud/latuitud y Zoom, que después recuperaremos desde el programa Delphi.
GEvent.addListener(map, "click", function (overlay,point)
GEvent.addListener(map, "zoomend", function (oldLevel, newLevel)
GEvent.addListener(map, "mousemove", function(latlng)
Aunque uno no esté muy familiarizado con el tema (yo mismo no lo estoy mucho ;-D ), no hacen falta muchas explicaciones para comprender los eventos; «Capturaremos» en OnClick, OnZoomEnd y OnMouseMove sobre el mapa. Aquí se puede acceder a la lista de eventos, métodos y propiedades de la clase Gmap.
En el caso del evento OnClick, por ejemplo, utilizaremos un código como este:
GEvent.addListener(map, "click", function (overlay,point){
if (point){
document.posicion.x.value=point.x
document.posicion.y.value=point.y
document.posicion.z.value=map.getZoom()
TipoMapa = map.getCurrentMapType().getName()
document.posicion.t.value=TipoMapa
}
Obtenemos información del punto actual (utilizando un parámetro) y el Zoom actual y el tipo de Mapa utilizando métodos de la clase GMap. Todos ellos se almacenan (como hemos comentado antes) en componentes de tipo Text, que nos sirven como «paso intermedio» para luego capturar esos valores de la página Web desde nuestro programa.
Como resultado final, os dejo el ejemplo que se adjunta con esta entrada. En él he incluído/integrado la parte de Geocodificación (visto en las entradas I y II), y el código y modificaciones necesarias para recuperar posición, Zoom y tipo de Mapa (este incluído a última hora) que el usuario selecciona en un Mapa.
La información de los lugares puede ser guardada y recuperada de un fichero de texto; La estructura es bastante simple y no hace falta mayor explicación. Se almacena en el mismo directorio de la aplicación y recibe el nombre de «Lugares.txt».
La información que podemos capturar del Mapa (utilizando la clase GMap) y de otros elementos que estén incluíds en el mapa (imágenes, líneas, marcas,…) es muy extensa y está detallada en las referencias de la API de Google Maps.
Personalmente, creo que las posibilidades de ampliación son muy grandes y bastantes más sencillas de lo que a priori puede parecer.
Espero que estas entradas hayan sido de utilidad. Como siempre cualquier comentario, corrección, sugerencia,… es bienvenida. ;-D
Un saludo.
ACTUALIZACIÓN (10 Febrero 2012): Dado el cambio de política de Google, ahora es necesario (obligatorio) colocar la key que se proporciona desde Google para poder utilizar la API. Es necesario modificar el fichero _mapa.txt del recurso y recompilarlo utilizando RC.CMD.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Después de las dos entradas sobre Codificacióin Geográfica (*), toca entrar de lleno en la Visualización e Integración de Google Maps en nuestro programa, que por otra parte era el objetivo inicial de esta serie de entradas. La Codificación Geográfica (obtención de Latitud y Loingitud a partir de una dirección postal) era un «escollo» a salvar en nuestro camino, hasta nuestro verdadero objetivo.
(*) Lo normal es tener en nuestros programas/aplicaciones una serie de direcciones postales o direcciones de correo que deseamos mostrar o señalar en un Mapa. Para utilizar esas direccones con la API de Google Maps necesitamos obtener las coordenadas de Latitud y Longitud apropiadas. Para ello ya vimos aquí (Codificación Geográfica – I) y aquí (Codificación Geográfica – II) cómo Google provee métodos para obtener las coordenadas de posicionamiento a partir de direcciones postales.
La idea para conseguir la integración es sencilla. Utilizar un componente TWebBrowser para mostrar una página HTML. Desde Delphi debemos «modificar» esa página para que muestre lo que nosotros deseemos.
Utilizando una sencilla página HTML con un Script, podemos visualizar el mapa correspondiente a coordenadas de Latitud y Longitud. Con un componente TWebBrowser podemos visualizar esa página dentro de una aplicación delphi.
La página en sí sólo contiene el Script para Visualizar un Mapa.
Dentro del Script vemos cómo se crea un elemento de tipo «map»:
map =new GMap2(document.getElementById("map"));
map = new GMap2(document.getElementById("map"));
Se añaden los controles que queremos visualizar en el mapa; En este caso, la selección para el tipo de mapa y control de zoom y posición grande.
// Controles del mapa
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl());
// Controles del mapa
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl());
El tipo de mapa que queremos que inicialmente aparezca (Mapa, visión por satélite o lo que Google llama híbrido).
// Tipo de mapa
map.setMapType(G_NORMAL_MAP);
// Tipo de mapa
map.setMapType(G_NORMAL_MAP);
Y por último colocamos la posición (latitud y longitud) donde queremos que el mapa salga centrado.
Pero no acaban aquí (ni mucho menos) las posibilidades de Google Maps. Una de las opciones, que dentro de lo sencillo, es muy interesante, es la inclusión de marcas señalando puntos del mapa o incluso ventanas de información no es mucho más complejo.
Para añadir una nueva marca, basta con conocer el punto exacto donde queremos situarla y utilizar un objeto GMarker para mostrarla en el mapa. En cuanto a la ventana, de información, sólo necesitamos el código HTML necesario para crearla.
En este ejemplo la ventana tiene un código sencillo (texto, imagen y link) que se almacena en una variable.
Para mostrarla basta con que utilicemos en método OpenInfoWindowHtml (cuyo nombre es bastante descriptivo, por cierto ;-D ) de la siguiente forma:
// Visualización de la mrcavar point =new GLatLng(41.381655,2.122829);var marker =new GMarker(point);
marker.openInfoWindowHtml(html);
// Visualización de la mrca
var point = new GLatLng(41.381655,2.122829);
var marker = new GMarker(point);
marker.openInfoWindowHtml(html);
Hay más opciones y basta con echarle una vistazo a la API de Google Maps para conocerlas. Además hay muchos ejemplos de utilización que pueden servir para aclarar los conceptos y para ver más cláramente cómo se utilizan los diferentes elementos.
Todo lo visto hasta aquí, está puesto en práctica en un ejemplo Delphi.
Podéis descargar el código completo (incluye EXE -comprimido UPX-) desde aquí:
<Descargar Ejemplo>
Y aquí acabamos esta entrada, que no será la última. Me dejo para una segunda parte, algunos detalles finales, como interactuar con el mapa y obtener datos de él y fusionar los dos conceptos vistos hasta ahora.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Hace unos días nos encontramos con el problema (no muy grande ;-D ) de añadir a una aplicación delphi existente la posibilidad de incluir una imagen seleccionada por el usuario. A priori la imagen era un JPG, de la cual se debía crear una miniatura (thumbnail) a unas dimensiones determinadas (180 x 115) y ambas debían subir a un directorio determinado. Ningun problema. Aquí mismo había un par de procedimientos de Domingo Seoane para redimensdionar una imagen.
En concreto modificando un poco el procedimiento Proporcional conseguí lo que necesitaba. Que si la imagen original no era exactamente de las mismas proporciones que la que necesitaba (miniatura) esta rellenara con un color «neutro» (en este caso el blanco) los bordes laterales.
// Esta otra mantiene la relacion entre alto y anchoprocedure Proporcional(Imagen: TGraphic; Ancho, Alto:Integer);var
Bitmap: TBitmap;
Rect:TRect;begin
Bitmap:= TBitmap.Create;try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;/// Calculos para que quede proporcionalif(Ancho/Imagen.Width) <(Alto/Imagen.Height)thenbegin
Alto:=Trunc((Ancho*Imagen.Height)/Imagen.Width);endelsebegin
Ancho:=Trunc((Imagen.Width*Alto)/Imagen.Height);end;// posición nueva// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left:=((Bitmap.Width- Ancho)div2);
Rect.Top:=((Bitmap.Height- Alto)div2);
Rect.Right:= Rect.Left+ Ancho;
Rect.Bottom:= Rect.Top+ Alto;// Color neutro para márgenes
Bitmap.Canvas.Brush.Color:= clRed;// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta otra mantiene la relacion entre alto y ancho
procedure Proporcional(Imagen: TGraphic; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
Rect:TRect;
begin
Bitmap:= TBitmap.Create;
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
/// Calculos para que quede proporcional
if (Ancho/Imagen.Width) < (Alto/Imagen.Height) then begin
Alto:= Trunc((Ancho*Imagen.Height)/Imagen.Width);
end
else begin
Ancho:= Trunc((Imagen.Width*Alto)/Imagen.Height);
end;
// posición nueva
// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left := ((Bitmap.Width - Ancho) div 2);
Rect.Top := ((Bitmap.Height - Alto) div 2);
Rect.Right:= Rect.Left + Ancho;
Rect.Bottom := Rect.Top + Alto;
// Color neutro para márgenes
Bitmap.Canvas.Brush.Color := clRed;
// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Hice un par de pruebas con imágenes y el resultado no fue exactamente lo que yo esperaba. El procedimiento era correcto, y funcionaba bien, pero las imagenes en minuatura presentaban Aliasing. Y siendo las miniaturas bastante pequeñas el efecto se notaba bastante.
Ampliando un poco la imagen y comparándola con una generada con cualquier programa sencillo de retoque fotográfico se apreciaba bastante la diferencia entre ambas.
Esto es lo que se conoce como aliasing. Se pueden encontrar múltiples definiciones y explicaciones de este problema en Internet (wiki), así que no explicaré aquí de que se trata.
APLICAR ANTIALIASING
La teoría dice que esto se soluciona aplicando algoritmos de altializasing, así me he puesto a hacer unas pruebas a ver qué resultado obtenía.Mi idea es modificar el color de cada uno de los pixels de la imagen teniendo en cuenta en color de los pixels que hay a su alrededor.
Qué pixels seleccionemos para ello y cuantos (distancia) determinará que el resultado sea más o menos satisfactorio, pero también afectará al tiempo de cálculo. Por lo que he leído esto es lo que se conoce como Supersampling/Multisampling.
Un ejemplo de diferentes selecciones de pixels se puede ver en la imagen siguiente:
En cada uno de estos casos se variará el color del pixel central teniendo en cuenta los colores de los pixels que hay a su alrededor.
A partir de aquí me he propuesto hacer algunas pruebas (sencillas) para comprobar si en los resultados se notaban cambios a simple vista.
PRUEBAS DE ALGORITMOS
Para los ejemplos he realizado una imagen sencilla, con varias líneas inclinadas, donde se aprecian bastantes «dientes de sierra» y algunas circunferencias. La imagen inicial es la que se ve en la figura siguiente con un tamaño inicial de 457 x 273 pixels.
La idea es reducir el tamaño de esa imagen hasta la mitad (más o menos) y a una cuarta parte aplicando antes un algoritmo de antialiasing sencillo escogiendo diferentes puntos para modificar el color de los pixels.
Para la reducción de tamaño, he utilizado un procedimiento estandard para reducir el tamaño de imágenes BPL utilizando (StretchDraw), pero en este caso, antes de hacer la reducción he probado a aplicar los algoritmos de AntiAliasing.
// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Para modificar el color lo que he probado es a sumar los colores de los puntos escogidos al del pixel actual y luego hacer la media para obtener un color resultante; Así por ejemplo, para calcular el nuevo color de un pixel teniendo en cuenta el pixel superior y el inferior de la misma columna, utilizo un código como este:
1
2
3
4
5
6
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 )div3;
G1:=Round(G1 + G2 + R3 )div3;
B1:=Round(B1 + B2 + b3 )div3;// color resultante
Result := RGB(R1,G1,B1);
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 ) div 3;
G1:=Round(G1 + G2 + R3 ) div 3;
B1:=Round(B1 + B2 + b3 ) div 3;
// color resultante
Result := RGB(R1,G1,B1);
Lo que he hecho en las pruebas es aplicar a la imagen, esta mismo procedimiento, pero teniendo en cuenta diferentes selecciones de puntos.
Seleccionando 2 puntos; Superior e inferior.
Seleccionando 4 puntos; Superior, inferior, izquierda y derecha.
Seleccionando 8 puntos. Los 8 puntos que hay alrededor del pixels actual.
Seleccionando 8 puntos y aplicando ponderación al actual. Utilizar los 8 pixels que hay alrededor del actual, pero aplicando más peso (más valor) al pixels actual (a su color) que a los del resto. En mi caso el pisel actual tiene un peso de 4, mientras que el resto queda con un pero 1.
En un primer ejemplo he aplicado los dos primeros (2 y 4 pixels), pensando que no habría grandes cambios y la verdad es qe me ha sorprendido, ya que tomando tan sólo 2 puntos ya se notan algunos cambios y tomando 4 las dioferencias ya son bastante apreciables.
El resultado obtenido por este ejemplo es el siguiente:
La imagen superior es el original (redimensionado tal como lo hace delphi), y las dos inferiores son a las que se les ha aplicado el procedimiento de Antialiasing antes de redimensionarlas. En una escogiendo 2 los pixels laterales y en la otra los 4 pixels que rodean al del cálculo. Superior, inferior, izquierdo y derecho.
Como se puede ver, con dos pixels únicamente, ya hay zonas (1, 3 y 5) donde se aprecian diferencias. Seguramente en estas más que en otras porque la selección de pixels no es homogénea (de ahí que en las líneas horizontales se aprecie más mejora).
Cuando se aplica el algoritmo teniendo en cuenta los 4 pixels de alrededor, se aprecia (2, 3, 4 y 5) ya bastantes diferencias.
En el segundo ejemplo he aplicado los 4 casos comentados antes.
El resultado de este segundo ejemplo es el siguiente:
En este caso entre los dos últimos no se aprecia diferencia visible, pero sí entre escoger 4 puntos y 8 puntos. Ver los puntos marcados como 1 y 3.
Dado que no se aprecian grandes diferencias entre los dos últimos, he integrado en un último ejemplo el redimensionado y el procedimiento de Antialiasing, de forma que este segundo se realice de forma automática.
Aunque el ejemplo que se ha desarrallo aquí y el procedimiento parece que funcionan de manera aceptable, hay que tener en cuenta otros factores a la hora de realizar un algoritmo más completo.
En nuestro caso la distancia de pixel utilizada (muestreo) es una distancia 1; es decir, hemos seleccionado los pixels que hay más cercanos al que vamos a modificar. Podemos seleccionar pixels de distancias mayores (2 y 3); De esta forma el resultado puede ser más correcto, aunque esto también tiene que ver con el porcentaje de reducción del tamaño.
No es lo mismo reducir una imagen a la mitad de su tamaño, que al 10% del tamaño original. Segun el caso el resultado puede ser mejor o peor si seleccionamos pixels a distancias 1,2 y 3 del pixels a calcular.
type
TRGBTripleArray =array[0..32767]of TRGBTriple;
PRGBTripleArray =^TRGBTripleArray;...// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;//····························································// Procedimiento de Antialiasing con Distancia=1procedure Antialiasing(bmp1, bmp2:TBitmap);var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;begin// Tamaño del bitmap destino
bmp2.Height:= bmp1.Height;
bmp2.Width:= bmp1.Width;// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];// reorrido para todos los pixelsfor Y :=1to(bmp1.Height-2)dobeginfor X :=1to(bmp1.Width-2)dobegin
R1 :=0; G1 :=0; B1 :=0;// los 9 pixels a tener en cuentafor j :=-1to1dobegin// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed+ SL2[X+j].rgbtRed+ SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen+ SL2[X+j].rgbtGreen+ SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue+ SL2[X+j].rgbtBlue+ SL3[X+j].rgbtBlue;end;// Nuevo color
R1:=Round(R1 div9);
G1:=Round(G1 div9);
B1:=Round(B1 div9);// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y]:= RGB(R1,G1,B1);end;// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];end;end;//···························································· begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
type
TRGBTripleArray = array[0..32767] of TRGBTriple;
PRGBTripleArray = ^TRGBTripleArray;
...
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
//····························································
// Procedimiento de Antialiasing con Distancia=1
procedure Antialiasing(bmp1, bmp2:TBitmap);
var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;
begin
// Tamaño del bitmap destino
bmp2.Height := bmp1.Height;
bmp2.Width := bmp1.Width;
// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];
// reorrido para todos los pixels
for Y := 1 to (bmp1.Height - 2) do begin
for X := 1 to (bmp1.Width - 2) do begin
R1 := 0; G1 := 0; B1 := 0;
// los 9 pixels a tener en cuenta
for j := -1 to 1 do begin
// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed + SL2[X+j].rgbtRed + SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen + SL2[X+j].rgbtGreen + SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue + SL2[X+j].rgbtBlue + SL3[X+j].rgbtBlue;
end;
// Nuevo color
R1:=Round(R1 div 9);
G1:=Round(G1 div 9);
B1:=Round(B1 div 9);
// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y] := RGB(R1,G1,B1);
end;
// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];
end;
end;
//····························································
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Permite guardar la posicion y tamaño de todos los componentes que se encuentran en el formulario. Utilizapara ello un fichero INI.
Basta con colocar el componente en el formulario y activarlo; Cuando la aplicación se cierra graba la posición/tamaño y al volver a ejecutarla los recupera de forma automática.
NOTA: Es indispensable para grabar la posición y tamaño de un componente que tenga definida la propiedad Name (para controles creados en RunTime).
Historial:
(version 1.4): Añadido un evento (OnNotExistComponent) que facilita el trabajo con componentes creados en tiempo de ejecución (runtime); Se han revisado los componentes para trabajar con Delphi 2005/2006.
(versión 1.3): El componente permite guardar propiedades de tipo TStrings (Lines, Items,…)
(versión 1.2): Para la versión 1.2 se ha añadido la posibilidad de poder añadir propiedades por parte del usuario que se grabarán (propiedad SavedProperties).
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Éste ejemplo muestra cómo «trocear» una imagen (un bitmap en éste caso) en n pequeñas imágenes a «modo de cudrícula»; El número de imágenes (de ancho y de alto) se puede configurar y finalmente las n partes de la imagen se guardan en disco.
Las imágenes resultantes de la división se guardan en disco.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…