Elisp: google-earth Command
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.
2014-11-06 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.
- Grab the current line, parse it, and get the main data of {title, longitude, latitude}. Set them to variables.
- Write a function that creates KML file, taking {title, longitude, latitude} as input. Call this function to create the KML file.
- Delete the 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 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 ( xtitle coordy coordx) (setq xtitle (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=\"" xtitle "\">✈</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〕