3ENGINE

Programación y otros cachivaches

Etiqueta: GIS

Tecnologia

Reducir el tamaño de un GeoJSON con TopoJSON


TopoJSON es una extensión de GeoJSON que introduce un nuevo tipo Topology que contiene objetos GeoJSON que codifica topologías.

Las geometrías se definen a partir de segmentos de líneas compartidas llamadas arcos. Las reglas topológicas exigen que los polígonos compartan una frontera común sin huecos ni superposiciones entre ellos. TopoJSON aprovecha estas reglas de modo que si dos polígonos comparten frontera (arco) entonces TopoJSON representa una única vez la frontera eliminando así redundancias. Gracias a la eliminación de redundancias y a la cuantificación de las coordenadas un fichero TopoJSON puede llegar a ser un 80% más pequeño que su equivalente en GeoJSON.

Un ejemplo sencillo para entender cómo funciona

Dada una forma sencilla cerca de las coordenadas de latitud=0° y longitud=0° donde tenemos dos polígonos, identificados con el color rojo y verde respectivamente:

Reducir el tamaño de un GeoJSON con TopoJSON

Su representación en GeoJSON es la siguiente:

{
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84"}
},
"features": [
{
"type": "Feature",
"properties": {"ID": 1},
"geometry": {
"type": "Polygon", 
"coordinates": [[[0, 1],[0, 0],[1, 0],[1, 1],[0, 1]]]
}
},
{
"type": "Feature",
"properties": {"ID": 2},
"geometry": {
"type": "Polygon",
"coordinates": [[[1, 1],[1, 0],[2, 0],[2, 1],[1, 1]]]
}
}
]
}

En su transformación a TopoJSON se definen tres arcos identificados con el color verde, azul y rojo, donde el arco en color rojo es un arco compartido por los otros dos polígonos.

Reducir el tamaño de un GeoJSON con TopoJSON

Su representación en TopoJSON queda como sigue:

{
"type": "Topology",
"crs": {
"type": "name",
"properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}
},
"transform": {
"scale": [1,1],
"translate": [0,0]
},
"arcs": [ 
          [[1,1],[0,-1]], 
  [[1,0],[-1,0],[0,1],[1,0]], 
          [[1,1],[1,0],[0,-1],[-1,0]]
],
"objects": {
"2cuadrados": {
"type": "GeometryCollection",
"geometries": [{
"arcs": [[0,1]],
"type": "Polygon",
"properties": {"ID": 1}
},
{
"arcs": [[2,-1]],
"type": "Polygon",
"properties": {"ID": 2}
}]
}
}
}
  • Línea 7: Para simplificar en el ejemplo no se ha aplicado una transformación lineal. El propósito de la transformación es cuantificar las posiciones para obtener una serialización más eficiente, mediante la representación de posiciones como números enteros.
  • Línea 11: Aquí se definen los arcos. En este caso hay 3 arcos. En cada uno de estos arcos la primera coordenada es cuantificada y el resto está definida respecto al punto anterior. El primer arco (rojo) va de la posición 1,1 a la 1,0. El segundo arco (verde) empieza en 1,0 y termina en 1,1 y el tercer arco (azul) empieza en 1,1 y termina en 1,0
  • Línea 19: Aquí se definen las geometrías. En este caso hay 2 geometrías. Cada una una de ellas se componen de varios arcos. La primera geometria se compone del arco [0] (rojo) y del arco [1] (verde). La segunda geometría se componen del arco [2] (azul) y del arco [-1] (rojo). El [-1] indica que la secuencia de las coordenadas en el arco debe ser invertida antes de la unión. Para evitar la ambigüedad con cero, se utiliza complemento a uno, de modo que [-1] representa el arco invertido [0] y así sucesivamente.

Exportar a TopoJSON

A continuación enumero algunas herramientas que permiten exportar a TopoJSON.

Topojson

Topojson es una herramienta de línea de comandos que permite los siguente formatos de entrada:

  • .json GeoJSON or TopoJSON
  • .shp ESRI shapefile
  • .csv comma-separated values (CSV)
  • .tsv tab-separated values (TSV)

Para instalar la herramienta necesitarás Node.js. Instala mediante NPM:

npm install -g topojson

Ejemplo básico que convierte el fichero GeoJSON input.json a un fichero TopoJSON output.json:

topojson -o output.json input.json

Mapshaper

Mapshaper es una herramienta online, que ademas podemos instalar y manejar desde la línea de comandos (mas información en una entrada anterior) que permite simplificar geometrías y exportar, entre otros formatos a TopoJSON.

Ejemplo básico que convierte el fichero GeoJSON input.json a un fichero TopoJSON output.json:

mapshaper input.json -o output.json format=topojson

Distillery

Distillery es una sencilla herramienta online que admite GeoJSON como formato de entrada, opcionalmente simplificar geometrías y exportar a TopoJSON.

Reducir el tamaño de un GeoJSON con TopoJSON

Geojson.io

Geojson.io es otra herramienta online avanzada para la edición GeoJSON con multitud de opciones, entre ellas la de exportar, entre otros formatos, a TopoJSON

Reducir el tamaño de un GeoJSON con TopoJSON

Utilizar TopoJSON en Leafleft

En un artículo anterior expliqué cómo crear un mapa con GeoJSON y Leafleft. Leafleft no soporta TopoJSON y será necesario transformar TopoJSON a GeoJSON. Para hacer esto, descarga la libreria topojson desde aquí y referenciala. Es posible que necesites referenciar también a jQuery:

<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script type="text/javascript" src="topojson.min.js"></script> 

Despues utiliza/adapta el siguiente código que descarga y transforma a GeoJSON:

// Transform topojson to geojson. 
// Copyright (c) 2013 Ryan Clark
L.TopoJSON = L.GeoJSON.extend({  
addData: function(jsonData) {    
if (jsonData.type === "Topology") {
for (key in jsonData.objects) {
geojson_ = topojson.feature(jsonData, jsonData.objects[key]);
L.GeoJSON.prototype.addData.call(this, geojson_);
}
}    
else {
L.GeoJSON.prototype.addData.call(this, jsonData);
}
}  
});

var geojson = new L.TopoJSON(undefined, {
style: style,
onEachFeature: onEachFeature
});

$.getJSON('mytopojson.topojson').done(addTopoData);

function addTopoData(topoData){  
geojson.addData(topoData);
geojson.addTo(map);
}



Tecnologia

Cómo reducir el tamaño de un GeoJSON


Uno de los problemas del webmapping es que necesita descargar la información espacial de un servidor. Y esto ha de ser lo más rápido posible. GeoJSON aun siendo un formato sencillo, dependiendo del número de coordenadas que contenga, puede ser muy pesado y por lo tanto el mapa puede tardar mucho tiempo en cargar.

Para la demo he aprovechado el GeoJSON de ejemplo que usé en un artículo anterior donde explico cómo crear un mapa con Leafleft y GeoJSON. Este GeoJSON ocupa de origen la friolera de 1.6 MB :-). Veamos cómo reducir el tamaño de un GeoJSON para mejorar la velocidad de nuestra aplicación webmapping:

Opción1: reducir el tamaño de un GeoJSON con QGIS (Quantum GIS)

En primer lugar cargamos el GeoJSON

Cómo reducir el tamaño de un GeoJSON

Después vamos a Vectorial > Herramientas de geometría > Simplificar geometrías. Seleccionamos la capa a simplificar, la tolerancia de simplificación, activamos y especificamos donde guardar el resultado y finalmente clic en aceptar:

Cómo reducir el tamaño de un GeoJSON

Como resultado de la simplificación QGIS genera un shapefile (.shp). Ahora sólo nos falta exportar el shapefile a formato GeoJSON, especificamos donde guardar el resultado y clic en aceptar. Opcionalmente podemos seleccionar otro sistema de coordenadas diferente al de origen (SRC) y la precisión de las coordenadas. Esto última puede ayudar a reducir aún más el GeoJSON.

Cómo reducir el tamaño de un GeoJSON

En mi caso, mientras que el GeoJSON original ocupaba 1.6MB el GeoJSON simplificado ahora ocupa 192KB.

Cómo reducir el tamaño de un GeoJSON

Opción2: reducir el tamaño de un GeoJSON con mapshaper

La principal ventaja de esta herramienta es que al ser online no necesita instalación aunque también podemos instalar la aplicación y manejar la herramienta desde la línea de comandos.

Acepta Shapefile, GeoJSON y TopoJSON como formatos de entrada y salida y tres métodos de simplificación de geometría. Seleccionamos el porcentaje de simplificación y exportamos al formato deseado.

Cómo reducir el tamaño de un GeoJSON

En mi caso, mientras que el GeoJSON original ocupaba 1.6MB el GeoJSON simplificado ahora ocupa 171KB.

mapshaper3

Opción3: reducir el tamaño de un GeoJSON con mapshaper desde línea de comandos

Una vez instalado node.js puedes instalar la última versión de mapshaper con npm:

npm install -g mapshaper

Existe una introducción a la herramienta por línea de comandos que muestra todo lo que puede hacer. En nuestro caso para simplificar el GeoJSON escribimos lo siguiente:

mapshaper demo.json -simplify 10% -o resultado.json




Tecnologia

Cómo crear un mapa con Leaflet y GeoJSON


Leaflet es una libreria de JavaScript de código abierto que permite construir aplicaciones de mapas web. Soporta HTML5, CSS3 y la mayoría de las plataformas móviles y de escritorio. Existen otras librerías, como OpenLayers o la API de Google Maps, pero Leaflet tiene la ventaja de ser muy compacta (carga rápido) y no requiere de conocimientos profundos de GIS.

leafleft

GeoJSON se encuentra dentro del grupo de los formatos de intercambio de datos geoespaciales mas extendidos. GeoJSON se basa en JSON. Permite la codificación de colecciones de estructuras de datos geográficos. Un objeto GeoJSON puede representar una geometría, una característica, o una colección de características. GeoJSON soporta los siguientes objetos geométricos: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection, Feature, FeatureCollection.

geojson

Obtener los datos GeoJSON para la demo

Podemos crear nuestro propio geojson mediante la herramienta online geojson.io, pero para nuestra demo obtendremos los datos de ejemplo de algunos de los lugares que siguiendo la inicitiva Open Data, ofrecen datos públicos de forma libre para todo el mundo, sin restricciones de derechos de autor, de patentes o de otros mecanismos de control. Por ejemplo, descargamos los municipios de la isla de La Palma. Lo abrimos:

{
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [{
"type": "Feature",
"properties": {
"OBJECTID": 1,
"SUPERFICIE": 135741394.267612,
"PERIMETRO": 66918.002370,
"ID": 1,
"MUNICIPIO": "EL PASO",
"CODIGO": 38027,
[...]
},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-17.884219, 28.616156], 
[-17.884263, 28.616166], 
[-17.884306, 28.616177],
[...]

«crs» indica el sistema de coordenadas de referencia utilizado. Usa WGS 84 como sistema de coordenadas, que es ampliamente utilizado por Google Maps, OpenStreetMaps, etc. «features» es una colección de características. Cada característica se compone de una colección de propiedades y una geometría, en este caso, un polígono.

Opcionalmente, para validar el GeoJson podemos utilizar por ejemplo geojsonlint. Esta herramienta online carga los datos en un mapa y valida que sean correctos.

Cómo crear un mapa con Leafleft y GeoJSON

Visualizar el contenido en un mapa con Leaflet

La manera mas sencilla de que Leaflet reconozca nuestros datos es convertirlos a código javascript. Aprovechando que JSON es compatible con javascript, esto es tan sencillo como editar el fichero, hacer que los datos GeoJSON estén asociados a una variable municipios y guardar el fichero con extensión .js:

var municipios = {
"type": "FeatureCollection",
[...]

El código es el siguiente, mas abajo comento el código:

<!DOCTYPE html>
<html>
<head>
<title>Municipios de La Palma</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css"/>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script src="demo.js" type="text/javascript"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script>
var map = L.map('map').setView([28.68, -17.85], 10);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
   maxZoom: 18,
   attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, '+
   '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, '+
   'Imagery © <a href="http://cloudmade.com">CloudMade</a>',
   id: 'mapbox.light'
}).addTo(map);

function onEachFeature(feature, layer) {
  if (feature.properties && feature.properties.MUNICIPIO) {
     var popupContent = "<p>Municipio: " + feature.properties.MUNICIPIO + "</p>";
     layer.bindPopup(popupContent);
  };
};

function style(feature) {
   return {
     weight: 2,
     opacity: 1,
     color: 'white',
     dashArray: '3',
     fillOpacity: 0.7,
     fillColor: '#FEB24C'
   };
};

L.geoJson(municipios, {
   style: style,
   onEachFeature: onEachFeature
}).addTo(map);

</script>
</body>
</html>
  • Línea 11: aquí referenciamos al geojson con los municipios.
  • Línea 14: map es la clase principal de Leafleft. Indicamos las coordenadas iniciales y el nivel de zoom por defecto.
  • Línea 16: añadimos una capa (layer) con el mapa. En este caso usamos las imágenes (tiles) de OpenStreetMap (OSM). Además especificamos el nivel máximo de zoom. Existen otros servidores de mapas de OSM, hay una lista de ellos en la Wiki de OSM o utiliza un comparador de mapas como map compare service de bbbike.org
  • Línea 24: función que muestra un popup con el nombre del municipio.
  • Línea 31: función con el estilo de cada uno de los features de la capa GeoJSON.
  • Línea 42: aquí creamos una capa de tipo GeoJSON, donde le pasamos la variable con los datos, le indicamos el estilo y le pasamos la función a llamar cuando el ratón haga click en una feature.

Y así quedaría el resultado:

Cómo crear un mapa con Leaflet y GeoJSON

Leer directamente GeoJSON con jQuery

En el anterior ejemplo hemos convertido el GeoJSON en código javascript. Pero dependiendo de nuestras necesidades, a veces esto no es posible y necesitaremos leer directamente el fichero en formato GeoJSON. Para hacer esto haremos uso de jQuery. El código es el siguiente, mas abajo comento el código:

<!DOCTYPE html>
<html>
<head>
<title>Municipios de La Palma</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css"/>
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
var map = L.map('map').setView([28.68, -17.85], 10);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
   maxZoom: 18,
   attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, '+
   '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, '+
   'Imagery © <a href="http://cloudmade.com">CloudMade</a>',
   id: 'mapbox.light'
}).addTo(map);

function onEachFeature(feature, layer) {
  if (feature.properties && feature.properties.MUNICIPIO) {
     var popupContent = "<p>Municipio: " + feature.properties.MUNICIPIO + "</p>";
     layer.bindPopup(popupContent);
  };
};

function style(feature) {
   return {
     weight: 2,
     opacity: 1,
     color: 'white',
     dashArray: '3',
     fillOpacity: 0.7,
     fillColor: '#FEB24C'
   };
};

$.getJSON("demo.json", function(data){
   L.geoJson(data, {
      style: style,
      onEachFeature: onEachFeature
   }).addTo(map);
});

</script>
</body>
</html>
  • Línea 12: aquí referenciamos a la libreria jQuery.
  • Línea 42: esta función lee el GeoJson, y despues, como el ejemplo anterior, creamos una capa de tipo GeoJSON, donde le pasamos la variable con los datos, le indicamos el estilo y le pasamos la función a llamar cuando el ratón haga click en una feature.



Tecnologia

Cómo guardar coordenadas geográficas en SQL Server


Introducción

SQL Server soporta dos tipos de datos espaciales:

  • geography: Superficies elipsoidales. Tiene en cuenta la superficie curva de la Tierra para sus cálculos. Se trabaja en longitud y latitud.
  • geometry: Superficies planas. La Tierra se trata como una proyección plana, no tiene en cuenta la forma elipsoidal de la Tierra. Adecuado para distancias cortas (edificios, calles,..)

Los tipos geography y geometry se crean a partir de objetos vectoriales, especificados en formato Well-Known Text (WKT) o Well-Known Binary (WKB).

Los objetos vectoriales admitidos son:

  • Point: una ubicación.
  • MultiPoint: una serie de puntos.
  • LineString: una serie de cero o más puntos conectados por líneas.
  • MultiLineString: un conjunto de linestrings.
  • Polygon: una región contigua descrita por un conjunto de linestrings.
  • MultiPolygon: un conjunto de polígonos.
  • GeometryCollection: una recopilación de tipos geométricos.

El modo mas sencillo de crear estos objetos es mediante el uso de los métodos de geografía
estáticos de Open Geospatial Consortium (OGC) incluidos en SQL Server que devuelven una instancia de geography o geometry a partir de una representación WKT:

  • STGeomFromText
  • STPointFromText
  • STMPointFromText
  • STLineFromText
  • STMLineFromText
  • STPolyFromText
  • STMPolyFromText
  • STGeomCollFromText

El método mas polivalente es STGeomFromText. Un ejemplo:

geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);

Donde el primer parámetro es un WKT con el objeto vectorial y el segundo parámetro es el identificador de referencia espacial (SRID).

El SRID corresponde a un sistema de referencia espacial. Sirve para identificar unívocamente el sistema de coordenadas utilizado para definir columnas de información espacial u objetos espaciales individuales en una columna espacial. Para averiguar la lista de SRID soportadas por SQL Server tenemos la vista sys.spatial_reference_systems:

select * from sys.spatial_reference_systems

Normalmente esto se compone de un sistema de coordenadas geodésicas, el más utilizado es el WGS84, con un patrón matemático de tres dimensiones que representa la tierra por medio de un elipsoide, mas un sistema de coordenadas cartesiano que pasa el modelo 3D a uno en 2D llamado proyección.

proy

Los dos mas comunes son el EPSG:3857 y el EPSG:4326, el primero es el usado en Google Maps, OpenStreetMap y otros mientras que el segundo es usado en GPS. La proyección de Mercator es las más utilizada y es en la que se basan EPSG:3857 y EPSG:4326. Representa la superficie esférica terrestre sobre una superficie cilíndrica, tangente al ecuador, que al desplegarse genera un mapa terrestre plano.

413px-Usgs_map_mercator.svg

Caso práctico

Despues de esta pequeña introducción 😉 un caso práctico.

1. Para guardar las coordenadas gráficas, he creado una tabla con un campo de tipo geography:

CREATE TABLE ejemplo (
[id] [int] NOT NULL PRIMARY KEY,
[geom] [geography] NOT NULL 
)

2. Para obtener coordenadas de prueba. En bing maps he añadido a Mis Lugares un área y dos puntos.

Cómo guardar coordenadas geográficas en SQL Server

Uno de los puntos se encuentra dentro del área y otro fuera. Por si alguien siente curiosidad el área delimita una parte del monasterio budista de Plana Novella. En concreto la zona donde están los mantras.

Ahora exporto las coordenadas a KML.

Cómo guardar coordenadas geográficas en SQL Server

Del KML me quedo con las coordenadas del área y monto el siguiente script para guardar las coordenadas geográficas en SQL Server:

INSERT INTO ejemplo (id, geom)
VALUES (1, 'POLYGON (( 1.8534866159243046 41.291925369663, 1.8540418331903874 41.291659349930804, 1.8537119214815556 41.291254272345995, 1.8531727974695622 41.29151021718217, 1.8534866159243046 41.291925369663))')
GO

3. Ahora quiero saber si hay una relación espacial entre el área y los dos puntos. En concreto quiero saber si alguno de los dos puntos se encuentra dentro del área. Hay un buen artículo que habla de este tema y que explica los métodos OGC disponibles.
SELECT id 
FROM ejemplo 
WHERE geom.STIntersects(geography::STGeomFromText('POINT (1.8535724466127812 41.29165531932045)', 4326)) = 1

SELECT id
FROM ejemplo
WHERE geom.STIntersects(geography::STGeomFromText('POINT (1.85385139635033 41.291979782656405)', 4326)) = 1