Xah Lee, 2006-12
This page shows a example of writing a emacs lisp function that creates a Google Earth file of a given location, and creates a link to the file, as well a link to Google Map. If you don't know elisp, first take a look at Emacs Lisp Basics.
If you don't know what Google Map or Google Earth is, see: Google Map and Google Earth.
I want to write a command so that if my cursor is on a text block such as this:
Las Vegas 36.1027,-115.1730 Las_Vegas.kml
After pressing a button, the block will become:
<div class="ggm"> <a href="http://maps.google.com/maps?z=11&amp;q=36.1027,-115.1730&amp;t=k" title="Las Vegas">Google Map↗</a> </div> <div class="gge"> <img src="../../ics/ge.png" alt="Google Earth icon"> <a href="Las_Vegas.kml" title="Las Vegas">Google Earth file</a> </div>
And a google earth file “Las_Vegas.kml” will be automatically created, linked by the above.
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 Map location of Las Vegas. The raw HTML is like this:
<p> <a href="http://maps.google.com/maps?z=15&q=36.1027,-115.1730&t=k" title="Las Vegas"> Google Map↗</a> </p>
The above would display in browser like this:
It is tedious to type all these characters to create a link, if i need to do it for many different cities. It would be nice, if all i have to do is to press a button and have the entire link inserted for me, then all i have to do is to edit the latitude and longitude part in the URL, and the title part. This is easily done in emacs!
The following elisp function will insert text for the Google Map link:
(defun insert-google-map-link () "Inserts html link markup for Google Map location." (interactive) (insert "<p> <a href=\"http://maps.google.com/maps?z=11&q=ttt&t=k\" title=\"ttt\"> Google Map↗</a> </p>") (re-search-backward "ttt" nil t) ) ; ttt is a place holder
Now, on my web site i also want to have a Google Earth file of the location, so that readers can click to download the file and launch Google Earth with the file to view the location. For example, in the HTML file i would have the following code:
<p> <img src="http://xahlee.org/Icons_dir/google_earth.png" alt="google earth icon"> <a href="las_vegas.kml" title="Las Vegas">Google Earth file</a> </p>
The above would display in browser like this:
Then, i need to actually create the Google Earth file. The Google Earth file is a XML based format with the suffix “.kml”. (See Keyhole Markup Language). I create the file “las_vegas.kml”, having this content:
<?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://earth.google.com/kml/2.1"> <Placemark> <name>Las Vegas</name> <description>Good for a visit!</description> <Point> <coordinates>-115.1730,36.1027,1000</coordinates> </Point> </Placemark> </kml>
It easy to write a elisp function “insert-google-earth-link” that will insert the link for me. It is also easy to write a function “insert-kml-template” that inserts the Google Earth KML template. These function will just be like insert-google-map-link shown previously.
So, now the procedure to create a Google Map and Google Earth of Las Vegas is:
As you can see, this is becoming tedious. If you are writing a travelog on your website and need to have many Google Map links and Google Earth files for various cities, going through the above process for each is no fun.
As we can see, a location's coordinates and name is manually entered repeatedly. It would be nice, if we can type out the city's name, the coordinates, and file name, and press a button, and have emacs create the Google Map link, Google Earth link, as well as the Google Earth KML file. For example, for the city Las Vegas, i type this block of text:
Las Vegas 36.1027,-115.1730 Las_Vegas.kml
Then, move the cursor somewhere in that block. Execute the function google-earth, and emacs will insert the Google Map link, Google Earth link, and create the Google Earth file as well.
We proceed to write this function.
First, we write a documentation for the function so that we know precisely what we want.
(defun google-earth () "Create a Google Earth file and link.\n The cursor must be on 3 lines separated by empty lines. The lines are: Name Coordinate file path (relative to the current file) For Example: Las Vegas 36.1027,-115.1730 Las_Vegas.kml google-earth will create the KML file and with proper html link to it in the current file." (interactive) )
The first step in implementing this function, is to have a code that grab the 3 lines and store them as values in variables.
After coding this for a while, i realized that its probably better to write a stand-along function to do this, since turning lines into values stored in variables can be useful for other programs. Here's our code:
(defun grab-lines (n) "Delete the next n lines and return a list where each element is a line." (interactive) (move-beginning-of-line 1) (let (t1 t2 cl (lns '())) (dotimes (x n) (progn (setq t1 (point)) (move-end-of-line nil) (setq t2 (point)) (setq cl (buffer-substring-no-properties t1 t2)) (delete-region t1 t2) (delete-char 1) (push cl lns) ) ) (setq lns (reverse lns)) ; (prin1 lns (current-buffer)) ; print result for testing purposes ))
In the above code, variables t1 and t2 are temp vars used to store positions for the beginning of line and ending of line. cl is a temp variable that stores the current line. “lns” is a temp var that stores the final list. The whole code is a simple loop and is easy to understand.
For example, you can test this code on the following lines
(grab-lines 4) line1 line2 line3
Now, we write the insert-google-earth-link.
(defun insert-google-earth-link (&optional title file-path) "Insert a customized HTML inline image markup for Google Earth icon and a link to a Google Earth file." (interactive) (let (title2 file-path2) (if title (progn (setq title2 title) (setq file-path2 file-path)) (progn (setq title2 "ttt") (setq file-path2 "ttt")) ) (insert "<div style=\"display:table;background-color:cornsilk\"> <img src=\"../../Icons_dir/google_earth.png\" alt=\"\"> <a href=\"" file-path2 "\" title=\"" title2 "\">Google Earth file</a> </div>\n") ) )
Note the optional argument “title” and “file-path”. So that, if called by a program and these are given, then a complete link with title and file path will be inserted. If called interactively, dummy text “ttt” is used for places the user need to fill in.
(We could write it so that it prompts user for input, but i find such interactive program less convenient to use.)
Now, we write a function to insert the Google Earth KML template. Like before, we use several optional parameters.
(defun insert-kml (&optional title latti longi) "Inserts a dummy Google Earth KML markup." (interactive) (let (title2 latti2 longi2) (if title ; if optional args not given, set them to dummy text (progn (setq title2 title) (setq latti2 latti) (setq longi2 longi) ) (progn (setq title2 "ttt") (setq latti2 "ttt") (setq longi2 "ttt") ) ) (insert "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <kml xmlns=\"http://earth.google.com/kml/2.1\"> <Placemark> <name>" title2 "</name> <description>Good for a visit.</description> <Point> <coordinates>" longi2 "," latti2 "</coordinates> </Point> </Placemark> </kml>") ) )
Finally, we have all our auxiliary components we need, we can go head and define our google-earth.
(defun google-earth () "Create a Google Earth file and link.\n The cursor must be on 3 lines separated by empty lines. The lines are: Name Coordinate file path (relative to the current file) For Example: Altamount Pass Wind Farm 37.7497,-121.6832 livermore/Altamount_Pass_Wind_Farm.kml google-earth will create the kml file and with proper html link to it in the current file." (interactive) (search-backward "\n\n") (search-forward "\n\n") (let (title latlon file-path sl vl latti longi) (setq sl '(title latlon file-path)) (setq vl (grab-lines 3)) (while sl (set (pop sl) (pop vl) ) ) (setq sl '(latti longi)) (setq vl (split-string latlon ",")) (while sl (set (pop sl) (pop vl) ) ) (insert-google-map-link title latlon) (insert-google-earth-link title file-path) (find-file file-path) (insert-kml title latti longi) (html-mode) (save-buffer) ) )
In this code, first we call “(search-backward "\n\n") (search-forward "\n\n")”. What these do is to move the cursor to the beginning of a text block, which is right after two blank lines.
Then, we define our temp vars. Here's what they mean:
“title” is the title of the Google Map/Earth. e.g. Las Vegas.
“latlon” is the lattitude and longitude string. e.g. 37.7497,-121.6832.
“file-path” is the file path to the Google Earth KML file.
“sl” and “vl” are temp vars. “sl” stands for symbols list, and “vl” stands for value list. They are used to assign a list of values to variables.
“latti” and “longi” and lattitudes and longitudes.
Now, in this block of code:
(setq sl '(title latlon file-path))
(setq vl (grab-lines 3))
(while sl (set (pop sl) (pop vl) ) )
We set sl to be a list of variable symbols. Then, we use grab-lines to get a list of values. The while function assigns the variables.
Then, we do the same by splitting the latitude and longitude string into separate variables and store them in latti and longi.
The rest of the code is trivial. We call insert-google-map-link to insert the Google Map link with proper fillers. We call insert-google-earth-link too. These will insert the Google Map link and the Google Earth link. We then call find-file to generate a new file, insert-kml to insert the KML template with proper fillers, then we make the buffer html-mode and save it.
If we want, we can close the buffer. But we want to leave it open so that the user might want to add descriptions for the KML file.
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 Beautiful.
Related essays: