Emacs Lisp: Writing a google-earth Function

Perm url with updates: http://xahlee.org/emacs/google-earth.html

Emacs Lisp: Writing a google-earth Function

Xah Lee, 2006-12, …, 2011-10-21

This page shows a example of writing a emacs lisp command that creates a Google Earth KML file of a given location, and creates a link to the file. If you don't know elisp, first take a look at Emacs Lisp Basics.

If you don't know what Google Earth is, see: Google Earth.

The Problem

Summary

Write a command “make-google-earth”. If my cursor is on a line like this:

Las Vegas,-115.1730,36.1027

After calling “make-google-earth”, the line will become:

<a href="../kml/las_vegas.kml" title="Las Vegas">⊕</a>

And a Google Earth KML file 〔Las_Vegas.kml〕 will be automatically created, linked by the above.

Detail

I often write travelogs on my website. If i traveled to Las Vegas, then my Las Vegas travelog page will have a link to Google Earth location of Las Vegas. The raw html looks like this:

<a href="../kml/las_vegas.kml" title="Las Vegas">⊕</a>

with proper Cascading Style Sheet (CSS), like this:

a[href$=".kml"], a[href$=".kmz"]
{background:url(img/google_earth_link.png)
no-repeat left center;
padding-left:25px;
background-position:left center !important}

It looks like this in a web browser:

If you move cursor to the link, it will show the title. Clicking on it will open the KML file and launch Google Earth to that location.

Also, i want emacs to automatically create the KML file for me. The KML file content would be like this:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Placemark>
<name>Las Vegas</name>
<description>
See: http://xahlee.org/Periodic_dosage_dir/las_vegas/20031020_vegas.html
</description>
<Point><coordinates>-115.1730,36.1027</coordinates></Point>
</Placemark>
</kml>

In the <description> tag, contains the URL of my web page that links to this KML file. So that in Google Earth, user can easily get back to my travelog page.

In summary, given a location name and its earth coordinates, i want emacs automatically create a KML file, and a link to the KML file. We proceed to write this function.

Solution

First, we write a documentation for the function so that we know precisely what we want.

(defun make-google-earth ()
"Create a KML file and replace the current line as a link to it.

The current line must have data of this format:
‹Name›,‹longitude›,‹latitude›

Example:
Las Vegas,-115.1730,36.1027

The line will be replaced like this:
<a href=\"../kml/las_vegas.kml\" title=\"Las Vegas\">⊕</a>

The KML file will be created in this dir:
〔~/web/xahlee_org/kml/〕."
;; )

Here are the major steps we need to do.

  • Grab the current line, parse it, and get the main data of “title”, “coordinate-x”, “coordinate-y”. Set them to variables.
  • Write a function that creates KML file.
  • Write a function that remove current line then insert the link to the newly created KML file.

To grab the current line and set it to variables, we can easily do like this:

  (setq p1 (line-beginning-position))
  (setq p2 (line-end-position))

  (setq dataText (buffer-substring-no-properties p1 p2 ))
  (setq dataTextTempList (split-string dataText ","))
  (setq title (pop dataTextTempList))
  (setq coord-x (pop dataTextTempList))
  (setq coord-y (pop dataTextTempList))

Once we got the data, we can delete the line, like this:

(delete-region p1 p2)

For inserting the link, we can write it like this:

(defun insert-google-earth-link (&optional title filePath)
  "Insert a HTML markup for link to a local Goole Earth file.
 TITLE is the title attribute in the anchor link.
 FILE-PATH is the path to the KML file.
Here's a sample inserted text:
<a href=\"../kml/las_vegas.kmz\" title=\"Las Vegas\">⊕</a>"
  (interactive)
  (when (not title) (setq title "�") )
  (when (not filePath) (setq filePath "�") )
  (insert "<a href=\"" filePath "\" title=\"" title "\">⊕</a>\n")
  )

To create a KML file, we can call “find-file”, then just insert a template text. Like this:

(find-file kmlFilePath)
(insert-kml title coord-x coord-y sourceFilePath)

The “insert-kml” can be written like this:

(defun insert-kml (&optional title longitude-lattitude sourceFilePath)
  "Insert a simple Google Earth KML markup template.
 TITLE is the name to use for the <name> tag.
longitude-lattitude is a vector [longitude lattitude]. They must be real numbers.
 SOURCEFILEPATH is the file that links to this kml file,
used in the <description> tag."
  (interactive)
  (let (coord-x coord-y)
    (when (not title) (setq title "�"))
    (if longitude-lattitude
        (progn
          (setq coord-x (elt longitude-lattitude 0))
          (setq coord-y (elt longitude-lattitude 1))
          )
      (progn
        (setq coord-x 0)
        (setq coord-y 0) ) )
    
    (insert "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<kml xmlns=\"http://www.opengis.net/kml/2.2\">
<Placemark>
<name>" title "</name>
<description>

See: http://xahlee.org/"
(when sourceFilePath (file-relative-name sourceFilePath "~/web/xahlee_org/" ))
"
</description>
<Point><coordinates>" (number-to-string coord-x) "," (number-to-string coord-y) "</coordinates></Point>
</Placemark>
</kml>\n")))

Complete Code

Here's the final “make-google-earth” code:

(defun make-google-earth ()
"Create a KML file and replace the current line as a link to it.

The current line must have data of this format:
‹title›,‹longitude›,‹latitude›

Example:
Las Vegas,-115.1730,36.1027

The line will be replaced to like this:
<a href=\"../kml/las_vegas.kml\" title=\"Las Vegas\">⊕</a>

The KML file will be created at:
 〔~/web/xahlee_org/kml/‹title›.kml〕."
(interactive)
(let (
      title coord-x coord-y
      dataText dataTextTempList
      kmlFilePath kmlDirRoot 
      sourceFilePath doit-p
      p1 p2
      )
  (setq p1 (line-beginning-position))
  (setq p2 (line-end-position))
  (setq dataText (buffer-substring-no-properties p1 p2 ))

  (setq kmlDirRoot "~/web/xahlee_org/kml/")
  (setq dataTextTempList (split-string dataText ","))
  (setq title (pop dataTextTempList))
  (setq coord-x (replace-regexp-in-string "°" "" (pop dataTextTempList)))
  (setq coord-y (replace-regexp-in-string "°" "" (pop dataTextTempList)))
  (setq sourceFilePath buffer-file-name)
  (setq kmlFilePath (concat (file-relative-name kmlDirRoot ) (replace-regexp-in-string " " "_" title) ".kml"))

  (setq doit-p t)
  (when (file-exists-p kmlFilePath)
    (setq doit-p nil)
    (setq doit-p (y-or-n-p (format "File exist at %s\nDo you want to replace it?" kmlFilePath)))
    )

  (when doit-p
    (delete-region p1 p2)
    (insert-google-map-link title (vector (string-to-number coord-x) (string-to-number coord-y)))
    (insert-google-earth-link title kmlFilePath)
    (find-file kmlFilePath)
    (erase-buffer)
    (insert-kml title (vector (string-to-number coord-x) (string-to-number coord-y)) sourceFilePath)
    (search-backward "<description>") (forward-char 14)
    (nxml-mode)
    (save-buffer)
    )
  ))

So now, we have a elisp function, that we can assign to a keystroke. Upon a press of button, it saves us a few hundreds of tedious error-prone keystrokes and file managing process.

Emacs is fantastic.

For KML files i created on my site with this function, see: Google Earth Files at XahLee.org.

Popular posts from this blog

11 Years of Writing About Emacs

does md5 creates more randomness?

Google Code shutting down, future of ErgoEmacs