Wednesday, February 21, 2007

Extracting coordinates from KML with XSL (e.g. for Google Maps)

Now that the Google Earth KML 2.1 format has an XML Schema, we can use XML validators to say for definite if a given XML file follows the rules of the KML format (i.e. it validates). An XML Schema gives an authoritative description of an XML format. XML Schema is not an easy format for humans to read. Worse still, KML 2.1 is a very complicated format and due to this, the XML Schema for KML 2.1 is also complex.

Many people who find my blog are looking to release the data that they have trapped inside KML files. The main problem they face is getting the "coordinates" data out of the KML. Mostly, people want to do this because they want to create Google Maps (GMaps) with that data. There are other reasons that we might want to extract this data and what I am about to present is a generic approach to extract this data (even if you do not plan to use it to produce Google Maps).

Firstly, thanks to the XML Schema, I can now examine the structure of the KML elements with more confidence. I spent a little time extracting what I considered to be the important parts of the KML 2.1 file format. There can be a great deal of information inside a KML file but I discovered that I am only interested in about 20% of what a KML file can currently offer. I am intentionally ignoring any of the format that I consider to be specifically useful to Google Earth (e.g. NetworkLinks, Overlays, style information, schema extension mechanisms, 3d models and anything involving altitude). This left me with this:

<kml> - top level root of the XML document
<kml> can contain any number of Feature elements.

Feature elements are <Document>, <Folder>, <Placemark>.
Feature elements can contain <name>, <address>, <description> and other sub-elements.

Geometry elements are <MultiGeometry>, <Point>, <LineString>, <LinearRing>, <Polygon>.

Feature elements

<Document> can contain any number of Feature elements.
<Folder> can contain any number of Feature elements.
<Placemark> can contain any number of Geometry elements.

Geometry elements

<MultiGeometry> can contain any number of Geometry elements.
<Point> contains a single <coordinates> element.
<LineString> contains a single <coordinates> element.
<LinearRing> contains a single <coordinates> element
<Polygon> contains a maximum of one <outerBoundaryIs> element.
<Polygon> contains any number of <innerBoundaryIs> elements

<outerBoundaryIs> elements contain a single <LinearRing> element
<innerBoundaryIs> elements contain a single <LinearRing> element

<coordinates> elements contain a space separated Cartesian coordinate value triples (e.g. "x1,y1,z1 x2,y2,z2"), if the element is contained inside a <Point>, this string is most likely to contain a single coordinate triple

Using this abridged description of KML, I can construct an XSLT that can extract co-ordinate information from any KML 2.1 format file (providing that the KML file does not extend the KML schema).


<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:kml="http://earth.google.com/kml/2.1">
<xsl:output method="text" version="1.0" omit-xml-declaration="yes" />

<xsl:variable name="cr"><xsl:text>
</xsl:text></xsl:variable>

<xsl:template match="/ | @* | * | comment() | processing-instruction() | text()">
<xsl:apply-templates select="@* | * | comment() | processing-instruction() | text()" />
</xsl:template>

<xsl:template match="kml:Document | kml:Folder | kml:Placemark">
<xsl:value-of select="name()"/>
<xsl:if test="string-length(kml:name) > 0">
Name: <xsl:value-of select="kml:name"/>
</xsl:if>
<xsl:if test="string-length(kml:address) > 0">
Address: <xsl:value-of select="kml:address"/>
</xsl:if>
<xsl:if test="string-length(kml:description) > 0">
Description: <xsl:value-of select="kml:description"/>
</xsl:if>
<xsl:value-of select="$cr"/>
<xsl:apply-templates />
</xsl:template>

<xsl:template match="kml:MultiGeometry | kml:LineString | kml:Point | kml:LinearRing | kml:Polygon">
<xsl:value-of select="name()"/><xsl:value-of select="$cr"/>
<xsl:apply-templates />
</xsl:template>

<xsl:template match="kml:outerBoundaryIs | kml:innerBoundaryIs">
<xsl:value-of select="name()"/><xsl:value-of select="$cr"/>
<xsl:apply-templates />
</xsl:template>


<xsl:template match="kml:coordinates">
<xsl:call-template name="split">
<xsl:with-param name="str" select="normalize-space(.)" />
</xsl:call-template>
</xsl:template>

<xsl:template name="split">
<xsl:param name="str" />
<xsl:choose>
<xsl:when test="contains($str,' ')">
<xsl:variable name="coord"><xsl:value-of select="substring-before($str,' ')" /></xsl:variable>
<xsl:variable name="first"><xsl:value-of select="substring-before($coord,',')" /></xsl:variable>
<xsl:variable name="second"><xsl:value-of select="substring-before(substring-after($coord,','),',')" /></xsl:variable>
X: <xsl:value-of select="$first" />
Y: <xsl:value-of select="$second" /><xsl:value-of select="$cr"/>
<xsl:call-template name="split">
<xsl:with-param name="str" select="normalize-space(substring-after($str,' '))" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:if test="string-length($str) > 0">
<xsl:variable name="first"><xsl:value-of select="substring-before($str,',')" /></xsl:variable>
<xsl:variable name="second"><xsl:value-of select="substring-before(substring-after($str,','),',')" /></xsl:variable>
X: <xsl:value-of select="$first" />
Y: <xsl:value-of select="$second" /><xsl:value-of select="$cr"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

With a little XSL know-how, the above skeletal XSLT can be modified to insert Google Maps JavaScript as appropriate.

There are approximate parallel between KML and GMaps e.g.:

I would stop short of creating a universal KML to GMaps solution because I have found specific requirements vary greatly. Larger quantities of co-ordinate data need special handling. The presentation of data, colours and style should be the decision of the developer. Although I have not produced a technique that people without XML, XSLT and JavaScript knowledge can use, I hope this is still useful to somebody.

4 comments:

ismjml said...

Thanks. This gives me something to start with. I have a sneaking suspicion however that Google is on the verge of adding native KML support to the Google Maps API, so it might be throwaway effort.
Note: Comment imported. Original by Looper at 2007-02-24 02:16

ismjml said...

Is it possible to translate CityGML to KML using XSLT?

Or simply GML to KML.

If you have any codes it would be very helpful.

I am trying to visualize cityGML in Google Earth. Please let me know

kibria14829@itc.nl
Note: Comment imported. Original by Shuman Kibria at 2007-02-26 01:51

ismjml said...

Hi Shuman,



Since CityGML and KML (COLLADA) are all XML, it would be inside the realms of possibility that an XSLT to achieve conversion could exist.





I know nothing about CityGML or 3d modelling. KML uses the COLLADA format for 3D models. I am not aware of any free software that you could use to convert between the formats but it seems there are some commercial offerings available including.





Snowflake software's GO Publisher also claims to support CityGML and KML

3D Geo KML Creator Professional for Google Earth claims to convert CityGML to KML


Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2007-02-26 09:39

ismjml said...

Hi,



I'm just starting out with all this XSLT and KML stuff, so you have sparked an interest. Could you please explain what is really going on in your example, because the syntax is more complex compared to what I've found online so far.



I'd like to be able to write my own stuff, so I can extra the information and stick it into a spreadsheet (Editgrid + XSL = dynamic Google Map).



Thanks in advance
Note: Comment imported. Original by Kristof at 2008-01-15 22:58