An OpenStreetMap App written in PicoLisp (Pt. 1)
5 min read
Let's write a little app that uses the GPS module of the smartphone. The app will display the current position on the map (Open Street Map) and fetch all ice cream cafes and supermarkets from the Open Street Map Overpass API.
But before we dive into Open Street Map, we need to understand how to interact with the GPS module of our smartphone.
In order to use the OpenStreetMap and Google Map functions, we need to import the
gis.l contains some GPS-specific functions such as defining latitude and longitude, calculating the distance between two coordinates and the interaction with the OpenStreetMap and Google Maps API.
We can import additional libraries by adding them to our
App.l file directly after the app name declaration. Also we need to add
gis to the App's name space in order to make the functions from
"Mia's Ice Cream Finder" "@lib/gis.l" (symbols 'icecream 'gis 'android 'pico)
Checking for permissions
Just like in the camera example, we have to verify that we actually have permission to access the phone's GPS. Just like there is a
camera? function to check the camera permissions, there is a
location? function to check the GPS permissions.
If GPS is not permitted, we could display a little warning "Please enable 'Location'":
(unless (location?) (<p> "tiny red" ,"Please enable \"Location\"" ) ) )
location? is defined in
Latitude and Longitude - some basics
GPS coordinates are represented in degrees, relative to the equator for the North/South coordinates (latitude), and relative to the prime meridian for West/East (longitude).
The latitude domain ranges from -90° (South Pole) to +90° (North Pole), and the longitude domain from -180° to 180°. Both -180° and +180° are indicating the same location, which is the "anti-meridian".
For example, Berlin is located at (52.52°, 13.404°). Since it is on the North-Eastern quarter of the world, both values are >0.
Getting the current location
We can get the current location with the
(gps) function. You can easily test it if you get a pseudoterminal from your PC to the PilBox and type
(gps) (of course, GPS permission must be available):
icecream: (gps) -> (143571890 . 192396667)
The function returns two integer values which stand for the Latitude and Longitude. However, the values are not represented in the same way as we normally expect. First of all, we need to fix the scale, as PicoLisp only uses Fixed-Point Calculation.
The scale is 6, which means that the formatted GPS output looks like this:
icecream: (format (car (gps)) *Scl )) -> "142.521890" icecream: (format (cdr (gps)) *Scl ) -> "193.404667"
Secondly, the latitude domain range at PicoLisp is mapped from [-90, 90] to [0, 180]. Likewise, the longitude domain range is mapped from [-180, 180] to [0, 360]. This simplifies the internal representation. So in order to re-transform the values, we need to subtract 90 degrees from the latitude and 180 degrees from the longitude, and adjust the scale.
icecream: (format (- (car (gps)) 90.0) *Scl ) -> "52.521890" icecream: (format (- (cdr (gps)) 180.0) *Scl ) -> "13.404667"
Let's get the current location with a button which adds both values to the global variables
(gui '(+Able +Button) '(location?) "Get my location" '(let G (gps) (setq *Latitude (car G) *Longitude (cdr G)) ) )
Displaying the current location on a map
There are two main choices if you want to use a map in your app:
Google Maps, which is arguably the bigger one, but it requires an API-Key and is not free (depending on the number of requests). You can read more about it on the Google Developer docs. Of course you can embed an iframe of a map showing the current location, but the user will not be able to interact with it.
The alternative choice is Open Street Map, which is entirely free, but the content might be less up-to-date and the data quality depends a lot on the number of volunteers who provided the data.
Variant 1: Google Maps
Google maps can be simply integrated with the
<google> function which takes four arguments: Latitude, Longitude, Width and Height. For example:
(<google> *Latitude *Longitude "100%" 400)
produces a map which fills the current container's width and 400 px height.
If you check the source code of
@lib/gis.l, you will see that this simply maps all values to an iframe:
(de <google> (Lat Lon DX DY) (prinl "<iframe width=\"" DX "\" height=\"" DY "\" frameborder=\"3\" \ src=\"https://www.google.com/maps?source=s_q&q=" (fmt Lat "," Lon) "&output=embed\"></iframe>" ) )
Variant 2: Open Street Map
Open Street Map can be integrated with the
<osm> function, which takes three arguments: Latitude, Longitude and the zoom level, which is defined in the Open Street Map documentation. A zoom level of 12 corresponds to a "town, or city district".
(<osm> *Latitude *Longitude 12)
Additionally, you can also pass more parameters defining the click and update behaviour, but it is not needed for our minimal example.
The nice thing about Open Street Map is that the developer is free to add additional icons or text to the map. For example, for our Ice Cream app, we can mark all ice cream locations with an ice cream icon, which is not possible in Google Maps.
This can be done via the
<poi> (point of interest) function, which takes as a minimum latitude, longitutde, image, an x and y offset, and a text with a text offset.
For example, these lines add a little pen icon and the word "Here" to the specified position on the map:
(<poi> *Latitude *Longitude "@img/go.png" "0.1" "1.0" "Here" -20 "black" )
With this, we can already write a little app that displays the current position on a map.
Next, let's try to retrieve the position of all listed ice cafes from the Open Street Map API and try to display it in our app.