Bill Farmer

Random thoughts on random subjects

Markdown OpenStreetMap Maps

by Bill Farmer. Categories: Hacking .

There does not seem to be a convention for showing maps or co-ordinates in Markdown, but almost anything gets put in square brackets [<anything>].

I had a request from Marco the other day to put OpenStreetMap maps in my Diary app using

  [<lat>,<lng>]

syntax. So I did a bit of a search to see if there was a convention in Markdown for co-ordinates and couldn’t find anything. So we seem to have broken new ground here.

The OpenStreetMap api for accessing their maps is quite straightforward, you just use http://www.openstreetmap.org/#map/<level>/<lat>/<lng> or various variations on that. However the syntax for their embedded iframe API doesn’t seem to be documented, but you can get the HTML for an embedded map from their map page and customise it.

<iframe width="425" height="350"
        src="https://www.openstreetmap.org/export/embed.html?
        bbox=3.1528186798095708%2C42.2577141531011%2C3.1886529922485356%2C42.27241862881183&amp;
        layer=mapnik"
        style="border: 1px solid black">
</iframe>
<br/>
<small>
  <a href="https://www.openstreetmap.org/#map=16/42.2651/3.1707">
    View Larger Map
  </a>
</small>

I have removed some non HTML5 code from the above. So the goal is to parse something that looks like [42.265,3.17] and produce something that looks like the above. The obvious tool in android Java is the Pattern and Matcher classes, which parse regular expressions. The regular expression for the pattern is

    "\\[(?:osm:)?(-?\\d+[,.]\\d+)[,;](-?\\d+[,.]\\d+)\\]"

The double backslashes are because it’s a string, so one of them will be removed before the matcher gets to see it. I also have to deal with European locales which use different conventions for co-ordinates. I added the option to have a prefix ‘osm:’, as using prefixes seems to be a markdown convention. It also allows for having different maps. The template for the iframe is

    "<iframe width=\"560\" height=\"420\"
    src=\"https://www.openstreetmap.org/export/embed.html?
    bbox=%f,%f,%f,%f&amp;layer=mapnik\"
    style=\"border: 1px solid black\">
    </iframe><br/>
    <small><a href=\"https://www.openstreetmap.org/#map=16/%f/%f\">
    View Larger Map</a></small>\n"

I have replaced the ‘%2C’s with commas, and changed the dimensions slightly, which seems to work OK. The android Java code to do this is

    // loadMarkdown
    private void loadMarkdown(String text)
    {
        markdownView.loadMarkdown(getBaseUrl(), markdownCheck(text),
                                  getStyles());
    }

    // markdownCheck
    private String markdownCheck(String text)
    {
        // Check for media
        text = mediaCheck(text);

        // Check for map
        return mapCheck(text);
    }

    // mapCheck
    private String mapCheck(String text)
    {
        StringBuffer buffer = new StringBuffer();

        Pattern pattern = Pattern.compile(MAP_PATTERN, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(text);

        // Find matches
        while (matcher.find())
        {
            double lat = 1.0;
            double lng = 1.0;

            try
            {
                lat = Double.parseDouble(matcher.group(1));
                lng = Double.parseDouble(matcher.group(2));
            }

            // Ignore parse error
            catch (Exception e)
            {
                continue;
            }

            // Create replacement iframe
            String replace =
                String.format(Locale.ENGLISH, MAP_TEMPLATE,
                              lng - 0.005, lat - 0.005,
                              lng + 0.005, lat + 0.005,
                              lat, lng);

            // Substitute replacement
            matcher.appendReplacement(buffer, replace);
        }

        // Append rest of entry
        matcher.appendTail(buffer);

        return buffer.toString();
    }

The input string is the markdown code to be parsed, the output is the same code with matched template substitutions. I have just added and subtracted 0.005 from the given co-ordinates to create a bounding box so the given point is in the centre. The API doesn’t seem to be too fussy. It is important to use the ENGLISH locale so the output uses dots for decimal points. The appendReplacement and appendTail Java methods are ready made for this pattern matching and replacement.

Update

After I had implemented this It was suggested that perhaps I should be using a geo Uri (![](geo:<lat>,<lng>)) for the map markdown syntax. I hadn’t thought of that, and it raises the possibility of receiving a geo Uri and turning it into a map. So I decided to convert existing [<lat>,<lng>] markdown to ![osm](geo:<lat>,<lng>) markdown, and convert that to an iframe as before. The code to do that is very similar to the above with updated patterns and templates.

        "geo:(-?\\d+[.]\\d+), ?(-?\\d+[.]\\d+)"

        "![osm](geo:%f,%f)"

The pattern is just to match a geo Uri, the template is to output a geo Uri.

In recent versions of android http:// URLs can be rejected by the web view, so I have updated the code to use https://. Also, to allow the use of http:// URLs in the markdown, I have added a line to the application element in the app manifest.

      android:usesCleartextTraffic="true"


See Also