Llevo un par de años recopilando mapas topográficos por internet (por la web y por la mula) y mi gran ilusión siempre ha sido tener una ortofoto de Barcelona colgada de la pared de mi comedor. Los que trabajen con gráficos sabrán que imprimir algo así con una calidad medio-decente implica una imagen de tamaño enorme... Pues bién, ya tengo el proyecto en fase de pre-impresión: tengo un CD con un archivo JPG de 140 MB!! Y una resolución de unos 22000x20000 píxeles.
Pero claro, no ha sido una tarea fácil. Con la aventura he descubierto cómo seleccionar una porción de foto del Institut Cartogràfic de Catalunya con sus parámetros de zoom y tamaño de la imagen, y he aprendido a modificar imágenes con la instrucción de línea de comandos convert, del paquete ImageMagick.
En éste artículo explico mis pasos hasta conseguir un enorme JPG con la ortofoto de Barcelona más grande que he visto.
Valoración de Opciones
En primer lugar cabe decir que he valorado varias opciones antes de optar por la que he tomado. Primero probé a guardar una imagen capturada de Google Earth, pero la resolución máxima ronda los 1000 píxeles de anchura y éso no me da para imprimir a un gran tamaño. Luego indagué un poco en la posiblidad de usar la API de Google Maps... si puedo sacar una imagen de 1000 píxeles de ancho, podría hacer un programa que dada una posición latitud x longitud vaya guardando fotos a máximo zoom como celdas en una tabla, es decir, sectorizando toda la zona de Barcelona en pequeñas porciones cuadradas. Pero sin llegar a mirarla, un compañero de trabajo familiarizado con la API (hola Víctor Guardiola
) me lo quita de la cabeza dándome una posiblidad mejor: usar los mapas del Institut Cartogràfic de Catalunya, que él usó en su momento para hacer algo parecido con Sant Cugat y las URLs de las imágenes son más bonitas y fáciles de usar... pues manos a la obra!
Búsqueda y pruebas de la URL para las ortofotos
El problema era que sólo tenía una pista: el ICC. Ninguna URL, ninguna sección que visitar... nada. Tal como entras en el ICC hay un buscador e introduciendo "Barcelona" te lleva al Visor Raster dónde tenemos una interficie al estilo Google Maps pero algo más simple (no permite rotar la imagen). Jugando un poco con él ves que puedes conseguir ortofotos de buena calidad pero el problema es el mismo: guardas la imagen a 500 píxeles de anchura. Entonces miré el código fuente de la página y busqué la URL de la imagen a ver si podía referirme sólo a la imagen. Después de asustarme con la inmensa cantidad de Javascript encontré ésto:
HTML:
-
<img width="500" style="border: 0px none " id="imgView" name="imgView" src="http://shagrat.icc.es/lizardtech/iserv/getimage?cat=mtc50m&item=iserv-catalog-index&cp=398985,4617056&lev=8&wid=500&hei=375&oif=jpg&geo=true" />
que limpiándolo un poco tenemos la URL de la imagen:
http://shagrat.icc.es/lizardtech/iserv/getimage?cat=mtc50m&item=iserv-catalog-index&cp=398985,4617056&lev=8&wid=500&hei=375&oif=jpg&geo=true
Si nos fijamos bién en la URL, tenemos varios parámetros que nos van a ser útiles para nuestros propósitos:
- cat: Éste es el tipo de imagen. Si queremos la ortofoto deberemos poner "orto5m" en vez de "mtc50m".
- cp: Aquí van las coordenadas separadas por una coma
- lev: Éste es el nivel de zoom. 8 es el menor zoom y 1 es el mayor
- wid: Es la resolución de la anchura en píxeles de la foto resultante. El valor máximo es 1200.
- hei: Es la resolución de la altura en píxeles de la foto resultante. El valor máximo es 1000.
Entonces, con unas pruebas más, vemos que podríamos hacer unas cuantas llamadas al servidor para que nos dé algunas fotos de la porción de terreno que le pedimos, y que éste responde bién.
Capturar todas las fotos necesarias.
Si lo pensamos un poco, como he dicho antes, podríamos coger la imagen de Barcelona y separarla en subzonas del tamaño que nos da cada imagen, como si fuera una cuadrícula. Para ello necesitamos saber sólamente las coordenadas de las esquinas para realizar un par de bucles anidados que pida imagen por imagen y se recalcule la siguiente coordenada a pedir. En mi caso, busqué las coordenadas siguientes:
- Esquina NO: 415867x4592290
- Esquina NE: 436155x4592290
- Esquina SO: 415867x4572066
- Esquina SE: 436155x4572066
Sabiendo ésto, podemos definir un pequeño script PHP que capture las imágenes. Primero, setaremos el tiempo límite del script a una cantidad razonablemente alta, para que no se nos interrumpa a media captura:
Luego, definiré las variables que utilizaré durante el proceso:
PHP:
-
$x_ini = 415867;
-
$x_fin = 436155;
-
$y_ini = 4592290;
-
$y_fin = 4572066;
-
$zoom = 1; //1 més petit, 8 més gran
-
$ample = 1200; //max 1200
-
$alt = 1000; //max 1000
-
-
$amplada_logo_icc = 60;
-
$offset_x = $ample - $amplada_logo_icc;
-
$offset_y = $alt;
Cabe decir que cada imagen lleva el logo del ICC. Si vamos a realizar una composición de éstas características el logo nos molesta porque estará presente en todas las fotos descargadas. Por éso, lo que hago es calcular el tamaño del logo (60 píxeles) y restarlo del offset horizontal. Así, todas las fotos se bajarán 60 píxeles más a la izquierda. Más adelante haremos unas preparaciones con las fotos para comernos el lado izquierdo (sólo 60 píxeles) quedando entonces todo perfecto.
Ahora, defino una función que me permita guardar en el disco las fotos a las que hacen referencia las URLs que voy a generar. Dicha función recibirá la URL a capturar y usaremos la función file_get_contents() para recoger los datos de la imagen y pasarlos a una cadena, que a su vez serán los datos que le pasaremos a file_put_contents() para que lo guarde en el disco. Además, necesitaremos definir un nombre de imagen descriptivo, ya que si no luego tendremos un lío de imágenes que no veas:
PHP:
-
function saveImage($url, $nombre)
-
{
-
$cadena = "";
-
-
$bytes = file_put_contents($nombre,$cadena);
-
return $bytes;
-
}
Y ya para acabar, el bucle en sí. Se trata de dos Fors, uno para las filas y otro para las columnas de celdas. Usaremos un contador en cada una de ellas para calcular las coordenadas necesarias:
PHP:
-
$contador = 0;
-
$bytes_escritos = 0;
-
//bucle per files
-
-
for($y=
0;
$y<=
ceil(($y_ini-
$y_fin)/
$offset_y);
$y++
)
-
{
-
$coord_y = $y_ini - ($y * $offset_y);
-
//bucle per columnes
-
for($x=
0;
$x<=
ceil(($x_fin-
$x_ini)/
$offset_x);
$x++
)
-
{
-
$coord_x = $x_ini + ($x * $offset_x);
-
-
//guardar el archivo
-
$bytes_escritos+= saveImage("http://shagrat.icc.es/lizardtech/iserv/getimage?cat=orto5m&item=iserv-catalog-index&cp=".$coord_x.",".$coord_y."&lev=".$zoom."&wid=".$ample."&hei=".$alt."&oif=jpg&geo=true","ortobarna_col_".$x."_fila_".$y.".jpg");
-
-
//realizamos una pausa, para no liarla con el servidor
-
-
-
$contador++;
-
}
-
}
Como siempre, lo primero de todo es inicializar las variables contadoras generales (aunque sólo sirven a nivel de resumen). Lo realmente complejo aquí es el cálculo del final del bucle y el cálculo de las coordenadas. Cojo por ejemplo el cálculo de las Xs:
PHP:
-
$coord_x = $x_ini + ($x * $offset_x);
Lo que hacemos es coger la coordenada inicial como base para todas las coordenadas, a la que le sumaremos el resultado de multiplicar el número de iteración con la anchura que cada foto tendrá. Así, la primera iteración, la zero, será sólamente la coordenada inicial, pero la segunda será la coordenada inicial más una vez el offset, la tercera será la coordenada inicial más dos veces el offset, y así sucesivamente... Para saber el final del bucle debemos saber el espacio que cruzaremos con el bucle y por éso restamos la coordenada final menos la coordenada inicial (ojo, en el eje de las Ys va al revés porque el eje está invertido). Luego, el resultado de la resta lo dividimos por la anchura de cada celda y así tendremos el número de iteraciones necesarias. Como ésta operación puede dar un número decimal debemos convertirlo a un entero (las coordenadas no aceptan decimales) y usamos ceil() para convertirlo al entero mayor (y así le damos algo de margen
):
PHP:
-
for($x=
0;
$x<=
ceil(($x_fin-
$x_ini)/
$offset_x);
$x++
)
Para acabar debo mencionar que se me pasó por la cabeza que desde el lado del servidor, al ejecutar el script, van a recibir un montón de peticiones en un mismo instante, y puede que el servidor tenga algún tipo de seguridad que nos bloquee el acceso. Para que ésto no ocurra o simplemente para prevenir una sobrecarga que nos dé alguna imagen errónea, le inserté un tiempo de espera entre cada celda. Por éso el sleep(2); para que se espere un par de segundos entre petición y petición.
Ya por último, yo soy de los que acostumbran a printar por pantalla resúmenes finales del proceso, para enterarme del trabajo realizado mediante unas estadísticas:
PHP:
-
print "Columnas: ".
ceil(($x_fin-
$x_ini)/
$offset_x).
"
-
";
-
print "Filas: ".
ceil(($y_ini-
$y_fin)/
$offset_y).
"
-
";
-
print "Totales: ".
ceil(($x_fin-
$x_ini)/
$offset_x) *
ceil(($y_ini-
$y_fin)/
$offset_y).
"
-
";
-
print "Valor del contador: ".
$contador.
"
-
";
-
print "Bytes Escritos: ".
$bytes_escritos.
"
-
";
Al ejecutar el script entero vamos a tener guardado en el disco (jeje, al cabo de unos cuantos minutos) todas las imágenes que necesitamos, como piezas de un puzzle.
Limpiar el logo de todas las fotos
Tenemos todas las fotos en el disco... pero todas llevan el logo! Tenemos que quitarlo... Lo que haremos es recortar 60 píxeles por la derecha en todas las fotos. Éso está ya planeado porque hemos calculado el offset de todas las fotos 60 píxeles más a la izquierda... quedará perfecto...
Pero cómo lo hacemos? Si abrimos las fotos una a una en el Photoshop nos vamos a morir del asco... Como somos usuarios de Linux (o deberíamos serlo
) tenemos a nuestra disposición una herramienta llamada convert procedente del paquete ImageMagick. Gracias a éste podremos realizar los cambios en todas las imágenes a la vez, que nos ahorrará un tiempo precioso:
for i in `ls *.jpg`;do convert $i -crop 1140x1000+0+0 +repage out/$i;done;
Hemos mostrado todas las imágenes JPG en el directorio (espero que no tuviérais ninguna que no fuera una pieza del mapa...) y para cada una de ellas recorta la imagen quedándose con una área de 1140x1000 (o lo que es lo mismo, eliminando 60 píxeles por la derecha), y lo guarda en un directorio llamado out dentro del actual, con las fotos recortadas usando el mismo nombre de foto.
Juntando las piezas del puzzle
Ahora ya sólo nos queda juntar las piezas. Sabemos que tenemos filas y columnas. Usaremos otra vez la herramienta convert, ya que permite adjuntar una foto con otra. Si usamos el modificador append con un signo de suma delante va a juntar las fotos horizontalmente, y si lo usamos con un signo de resta lo hará verticalmente. Yo uní las piezas primero en columnas para luego proceder a juntarlas todas en una sola fila.
Para juntar las fotos en columnas:
convert -append ortobarna_col_0_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_0.jpg
convert -append ortobarna_col_1_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_1.jpg
convert -append ortobarna_col_2_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_2.jpg
convert -append ortobarna_col_3_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_3.jpg
convert -append ortobarna_col_4_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_4.jpg
convert -append ortobarna_col_5_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_5.jpg
convert -append ortobarna_col_6_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_6.jpg
convert -append ortobarna_col_7_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_7.jpg
convert -append ortobarna_col_8_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_8.jpg
convert -append ortobarna_col_9_fila_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}.jpg col_9.jpg
Vale, vale, se podría haber bucleado ésto, pero ya eran unas horas que mis neuronas no me dejaban pensar demasiado... Aquí que cada uno aporte un poquito
Ahora tenemos 18 columnas de fotos. Podemos verlas... es gracioso ver una foto de Barcelona a base de secciones verticales... Pero nos queda la tarea más ardúa... Juntar las columnas. No es que sea difícil... de hecho es igual de fácil que juntar fotos en columnas, pero tenemos que pensar que el tamaño en memoria de éstas fotos es importante y si juntar 21 fotos 500K en una columna no es mucho, juntar 18 columnas de 9 MB no es una tontería. En mi caso probé de utilizar la misma herramienta de la misma forma pero para juntarlo en una fila:
convert +append col_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18}.jpg barcelona.jpg
Pero al cabo de cierto tiempo mi Ubuntu me cerraba el proceso por exceso de memoria. Tenía que buscarme algun truco... Y lo encontré. Resulta que el convert tiene un parámetro para limitar el uso de memoria. Cuando ésa memoria se sobrepasa se usa memoria de disco, que irá más lento, pero almenos acabará el proceso. En mi caso usé 200 MB como límite de memoria:
convert -limit memory 200 +append col_{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18}.jpg barcelona.jpg
Et Voilà, ya tenemos ortofoto de Barcelona a resolución gigante. Cuando lo tenga impreso y colgado a la pared haré una foto para demostrarlo
Salud!
Edito: La función encargada de capturar la imagen de una URL y guardarla en el disco utiliza una instrucción sólo para PHP 5 y superiores. La función se puede construir usando otras instrucciones para que funcione en versiones anteriores de PHP. Gracias a xaumet!
PHP:
-
function saveImage($url, $nombre)
-
{
-
$cadena = "";
-
-
$bytes =
fopen($nombre,
"w");
-
-
-
return $bytes;
-
}
Bueno, pues os cuelgo parte del resultado. La imagen resultante está orientada al Norte, situando Barcelona en diagonal con el mar. Yo quería tener el mar debajo y la montaña arriba, así que está rotado, cropado y escalado para que quepa aquí. La idea es hacer el cuadro más o menos así... Puede que quite el Llobregat, pero quiero que salga el aeropuerto
Escribe un Comentario