ELisp: google-earth Command

By Xah Lee. Date: . Last updated: .

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 ELisp: Quick Start.

Note: a new command more suitable today is this: ELisp: Earth Coordinate to Google Map Link

Problem

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

Las Vegas,-115.1730,36.1027

After Alt+x 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, it 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 to automatically create a KML file, and a link to the KML file.

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:
‹title›,‹longitude›,‹latitude›

Example:
Las Vegas,-115.1730,36.1027

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

The KML file will be created at:
 ~/web/xahlee_org/kml/‹title›.kml"
)

Here are the major steps we need to do.

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 inputStr (buffer-substring-no-properties p1 p2 ))

  (setq titleCoordList (split-string inputStr ","))

  (setq title (elt titleCoordList 0))
  (setq coord-x (elt titleCoordList 1))
  (setq coord-y (elt titleCoordList 2))

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 "x") )
  (when (not filePath) (setq filePath "x") )
  (insert (format "<a href=\"%s\" title=\"%s\">⊕</a>\n"  filePath title))
  )

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 "x"))
    (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=\"…/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
      inputStr titleCoordList
      kmlFilePath kmlDirRoot
      sourceFilePath doit-p
      p1 p2
      )
  (setq p1 (line-beginning-position))
  (setq p2 (line-end-position))
  (setq inputStr (buffer-substring-no-properties p1 p2 ))

  (setq kmlDirRoot "~/web/xahlee_org/kml/")
  (setq titleCoordList (split-string inputStr ","))
  (setq title (elt titleCoordList 0))
  (setq coord-x (replace-regexp-in-string "°" "" (elt titleCoordList 1)))
  (setq coord-y (replace-regexp-in-string "°" "" (elt titleCoordList 2)))
  (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 command that we can assign to a hotkey [see Emacs Keys: Define Key] . Then, pressing a key, it saves us a few hundreds of tedious error-prone keystrokes and file managing process.

Emacs is fantastic.

insert-google-map-link

Note that in the above there's a line insert-google-map-link. Sometimes you don't want Google Earth, but a link to Google Maps. This is very convenient for most people, because they may not have Google Earth installed. Here's insert-google-map-link code:

(defun insert-google-map-link (&optional title longitude-lattitude)
  "Insert HTML link to Google Map.

Takes 2 optional parameters: title and [longitude lattitude]

The longitude must be a decimal number. Positive signifies east, negative signifies west.
Similar for lattitude.

Example of inserted text:
 <a href=\"http://maps.google.com/maps?q=25.269536%2C82.990723\" title=\"Petrified Forest National Park\">✈</a>"
  (interactive)
  (let ( $title coordy coordx)
    (setq $title (if title title ""))
    (if longitude-lattitude
        (progn
          (setq coordx (elt longitude-lattitude 0))
          (setq coordy (elt longitude-lattitude 1))
          )
      (progn
        (setq coordx "coordx")
        (setq coordy "coordy") ) )
    (insert "<a href=\"http://maps.google.com/maps?q=" (number-to-string coordy) "%2C" (number-to-string coordx) "\" title=\"" $title "\">✈</a>\n")))

The weird ξ you see in my elisp code is Greek x. I use Unicode char in symbol name for easy distinction from builtin symbols. You can just ignore it. [see Variable Naming: English Words Considered Harmful]