2

I'm trying to create a new command in LaTeX that creates a Google Map URL pointing to the coordinates associated with the place specified as a parameter in the called command (a sort of "table lookup" function). The places and the relative coordinates are stored inside a coords.CSV file, and must be read using the datatool package.

The Google Map URL should be structured like this one:

https://www.google.com/maps/?q=<LAT>,<LNG>

where <LAT> and <LNG> are the latitude and longitude coordinates loaded from the coords.CSV file, which is structured in this way:

Place,LAT,LNG
Test,42.0000,42.0000
...

This is how the command was defined:

\usepackage{datatool}
\newcommand{\coords}[1]{
    % Loads the CSV
    \DTLsetseparator{,}
    \DTLloaddb{coords}{doc/coords.csv}
% Assigns the coordinates to the variables \LAT and \LNG, relative to specific place (the parameter #1)
\def \LAT {\DTLfetch{coords}{Place}{#1}{LAT}}
\def \LNG {\DTLfetch{coords}{Place}{#1}{LNG}}

% Generates the URL pointing to Google Maps
Place: \href{https://www.google.com/maps/?q=\LNG ,\LNG}{#1}

}

Finally, I use the new command in this way:

\coords{Test}

I've managed to correctly load the coordinates of the place called inside the command (in this case "Test"), but when I try to generate the URL, LaTeX gives me a lot of errors, most of which are ! Undefined control sequence. If I remove \LAT and \LNG from the line where the URL is generated (inside the command definition), I don't get any error, but of course the URL does not contain any coordinates, since they are stored inside the \LAT and \LNG variables.

Is there a way to properly generate the URL using the defined variables inside the \href command?

This is a test example:

\documentclass[a4paper,10pt]{article}

\usepackage{hyperref}
\usepackage{datatool}

\newcommand{\coords}[1]{ % Loads the CSV \DTLsetseparator{,} \DTLloaddb{coords}{coords.csv}

% Assigns the coordinates to the variables \LAT and \LNG, relative to specific place (the parameter #1)
\def \LAT {\DTLfetch{coords}{Place}{#1}{LAT}}
\def \LNG {\DTLfetch{coords}{Place}{#1}{LNG}}

% Generates the URL pointing to Google Maps
Place: \href{https://www.google.com/maps/?q=\LAT ,\LNG}{#1}

}

\begin{document}

\coords{Test}

\end{document}

  • 1
    As always on the site please provide a full but minimal self contained example that others can copy and test as is. That makes it a lot easier to help. Note also that the filtered log from your editor (the image you show here) often also does not help and it has a tendency to leave out important details, so a full log of a minimal example is better. – daleif Dec 04 '20 at 09:30
  • I've provided the test example as you asked. However, I can't upload the full log since it's long 1477 lines – Trial4life Dec 04 '20 at 09:47
  • We don't have your data (hence the term self contained), please also provide sample data – daleif Dec 04 '20 at 10:17
  • I did provide the sample data at the top of the post. I write it again here: coords.csv: Place,LAT,LNG Test,42,42 I think it shouldn't be too difficult to recreate a csv file with just 2 lines. – Trial4life Dec 04 '20 at 10:23
  • My bad, did not see that, you know you can add it you the tex file using the filecontents* env (it takes a mandatory filename as its argument) – daleif Dec 04 '20 at 10:25

2 Answers2

4

You can use the same trick as in my answer to retrieving substring of `\DTLfetch`

\begin{filecontents*}{\jobname.csv}
Place,LAT,LNG
Test,42.0000,42.0000
\end{filecontents*}

\documentclass{article} \usepackage{datatool} \usepackage{hyperref}

\DTLsetseparator{,} \DTLloaddb{coords}{\jobname.csv}

\newcommand{\DTLfetchsave}[5]{% see https://tex.stackexchange.com/a/335489/4427 \edtlgetrowforvalue{#2}{\dtlcolumnindex{#2}{#3}}{#4}% \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#2}{#5}}% \let#1\dtlcurrentvalue }

\newcommand{\coords}[1]{% \DTLfetchsave{\LAT}{coords}{Place}{#1}{LAT}% \DTLfetchsave{\LNG}{coords}{Place}{#1}{LNG}% % Generates the URL pointing to Google Maps Place: \href{https://www.google.com/maps/?q=\LAT,\LNG}{#1}% }

\begin{document}

\coords{Test}

\end{document}

I used \jobname to avoid clobbering my files. You can use whatever file name you want for the database.

Load the database once, not everytime you call \coords.

egreg
  • 1,121,712
  • Seems I overlook something subtle, thus I ask: Why \dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{#2}{#5}}%...\let#1\dtlcurrentvalue instead of \dtlgetentryfromcurrentrow{#1}{\dtlcolumnindex{#2}{#5}}% ? – Ulrich Diez Dec 04 '20 at 12:53
  • @UlrichDiez I'm not sure the long command existed when I wrote the other answer; and “if it ain't broken, don't fix it”. – egreg Dec 04 '20 at 12:54
  • According to the datatool-manual you can use \dtlgetentryfromcurrentrow with whatsoever control-sequence - you are not bound to using \dtlcurrentvalue. "long command" - are you saying you don't wish to prevent the possibility of redefining \par? – Ulrich Diez Dec 04 '20 at 12:58
  • @UlrichDiez I was lazy and didn't want to copy \dtlgetentryfromcurrentrow and \dtlgetentryfromcurrentrow from your comment. ;-) – egreg Dec 04 '20 at 13:00
  • Thanks for the clarification. (Time to look in the mirror and do a facepalm to myself.) ;-) ... – Ulrich Diez Dec 04 '20 at 13:02
  • @egreg thanks a lot for the help, your trick worked flawlessly! – Trial4life Dec 04 '20 at 13:06
3

I suggest the following approach:

\documentclass[a4paper,10pt]{article}

% Let's create the file coords.csv - the directory ./doc must exist % and writing-permission for that directory must be given!!! % An already existing file won't be overwritten by the % filecontents-environment (unless you provide the "overwrite"-option) % and you will be informed about the fact that the file already % exists via a message in the .log-file only. You won't get a % message on the terminal/console. \begin{filecontents}{doc/coords.csv} Place,LAT,LNG Test,42.0000,42.0000 \end{filecontents*}

\usepackage{hyperref}
\usepackage{datatool}

\newcommand{\coords}[1]{%%% \begingroup % Load the CSV only if database "coords" doesn't already exist: \DTLifdbexists{coords}{}{%%% %\DTLsetseparator{,}% Comma is the default, so this probably is not needed. \DTLloaddb{coords}{doc/coords.csv}%%% }%%% % Assign the coordinates of the place whose name is denoted by the % parameter #1 to the macros \LAT and \LNG: \edtlgetrowforvalue{coords}{\dtlcolumnindex{coords}{Place}}{#1}%%% \dtlgetentryfromcurrentrow{\LAT}{\dtlcolumnindex{coords}{LAT}}%%% \dtlgetentryfromcurrentrow{\LNG}{\dtlcolumnindex{coords}{LNG}}%%% %%% % Use the name (denoted by #1) of the place as a hyperlink leading % to the corresponding URL of Google Maps: Place: \href{https://www.google.com/maps/?q=\LAT,\LNG}{#1}%%% \endgroup }%%%

\begin{document}

\coords{Test}

\end{document}

I suggest this approach because with your code there are some issues:

Issue 1:

Your \coords-command produces unwanted space-tokens and \par-tokens:

I re-write it with comments indicating where these unwanted tokens come into being:

\newcommand{\coords}[1]{ %<- unwanted space-token yields horizontal space in horizontal mode
    % Loads the CSV
    \DTLsetseparator{,} %<- unwanted space-token yields horizontal space in horizontal mode
    \DTLloaddb{coords}{doc/coords.csv} %<- unwanted space-token yields horizontal space in horizontal mode
        %<- unwanted control word token \par
    % Assigns the coordinates to the variables \LAT and \LNG, relative to specific place (the parameter #1)
    \def \LAT {\DTLfetch{coords}{Place}{#1}{LAT}} %<- unwanted space-token yields horizontal space in horizontal mode
    \def \LNG {\DTLfetch{coords}{Place}{#1}{LNG}} %<- unwanted space-token yields horizontal space in horizontal mode
        %<- unwanted control word token \par                
    % Generates the URL pointing to Google Maps
    Place: \href{https://www.google.com/maps/?q=\LAT ,\LNG}{#1} %<- unwanted space-token yields horizontal space in horizontal mode
}

Issue 2:

The hyperref-manual says that tokens in the URL-argument of \href must be fully expandable.

Your commands \LAT and \LNG are not fully expandable because their definition contains the control word token \DTLfetch while the manual of the datatool-package clearly says that

\DTLfetch{students}{regnum}{\RegNum}{forename}
is equal to

\dtlgetrowforvalue{students}{\dtlcolumnindex{students}{regnum}}{\RegNum}%

\dtlgetentryfromcurrentrow{\dtlcurrentvalue}{\dtlcolumnindex{students}{forename}}% \dtlcurrentvalue

which indicates that \DTLfetch (and thus every macro whose expansion at some stage yields the token \DTLfetch) is not fully expandable as a macro \dtlcurrentvalue gets defined by \dtlgetentryfromcurrentrow.

Issue 3:

I doubt that it is necessary to load the database with each call to \coords.

Issue 4:

You use the command \DTLsetseparator for setting the separator for entries of a database to comma although this is the default. So this probably is obsolete.

Ulrich Diez
  • 28,770