Pascal and Mapbox
Part 2: macOS and Lazarus
Contents
Introduction
Setting up
Installing in Lazarus
Using the TMbMap custom control
Deploying your app
BoxCast LCL: an example app
Misc. notes
Introduction
On macOS, Mapbox
is a 64-bit framework. There are two ways you can use it with Pascal:
- Xcode (what the Mapbox macOS SDK was designed for)
- Lazarus (64-bit Cocoa widgetset to use the framework, but any platform
can be used for map design)
We'll start with Lazarus and cover Xcode in Part 3.
With Lazarus, you can use the TMbMap custom control, which wraps
portions of the Mapbox framework. You can install and use this control in
Lazarus on any platform, but it will display a map only in apps compiled
with the 64-bit Cocoa widgetset.
Setting up
Here are the steps. If you're working on a non-Mac platform, you only have
to do steps 4 and 5.
- Sign up for a Mapbox account.
This is free to start using.
- Download the Mapbox macOS SDK. These notes are based on work with the
version 0.6.1 SDK, which you can download from
here.
Double-click the .zip and move the resulting mapbox-macos-sdk-0.6.1
folder anywhere you like.
- Download the macOS framework Pascal interface units (AppKit, etc.) from
here.
Double-click the .zip and move the resulting MacOS_10_10-master
folder anywhere you like.
- Download the macOS "extras" .zip from
here.
Don't let the name fool you. On non-Mac platforms, this is just a
dummy package that the custom control package requires.
Double-click the .zip and move the resulting MacOS_10_10-extras
folder anywhere you like.
On Mac, copy the folder's files to the MacOS_10_10-master folder.
- Download the Pascal SDK for Mapbox from
here.
Double-click the .zip and move the resulting mapbox-macos-pascal
folder anywhere you like.
Installing in Lazarus
To add the TMbMap custom control to Lazarus, you compile a series of
packages.
- Mac users with Lazarus IDE that was
built with 64-bit Cocoa:
Copy Mapbox.framework from the
mapbox-macos-sdk folder to your /Library/Frameworks folder.
Note that when rebuilt, this will link Lazarus to Mapbox.framework,
so don't remove it without first uninstalling the mapbox_ctrls package.
All other users: Skip this step.
- In Lazarus, choose Package | Open Package File and select
macos_common_frameworks.lpk in the MacOS_10_10-extras folder.
Click Compile, but don't install it.
- In Lazarus, choose Package | Open Package File and select
mapbox_framework.lpk in the mapbox-macos-pascal folder.
Click Compile, but don't install it.
- In Lazarus, choose Package | Open Package File and select
mapbox_ctrls.lpk in the mapbox-macos-pascal/mapbox_ctrls folder.
Click Compile, then click Install to rebuild Lazarus. Once Lazarus
restarts, you'll see a Mapbox tab on the components palette.
Using the TMbMap custom control
In Lazarus, create a new project, then add a TMbMap control to the form.
Compile and run the app. When it appears, the map will be blank.
For a 64-bit Cocoa app, look in the Console app's log. You'll see
a message indicating that no access token has been specified.
Sign in to your Mapbox account and create an
access token. Typically you'll create one for each project. That way, you can
delete or change a project's token without affecting any other projects.
Now add this line of code to your project's .lpr source file (before the
first CreateForm line), substituting your access token (copy and paste):
TMbMap.SetAccessToken('your-access-token-goes-here');
You'll also need to add the MbMap unit to the .lpr file's uses.
Compile and run the app again. You should now see a map of the world.
Map details
To have the map come up on a specified location, set the map control's
Latitude, Longitude and ZoomLevel properties. For example, to zoom in on
the San Juan Mountains of southwest Colorado in the U.S. and display
a detailed map, use these values:
- Latitude: 37.92
- Longitude: -107.74
- ZoomLevel: 9
- StyleURL: mapbox://styles/mapbox/satellite-streets-v10
Deploying your app
Your test app is currently loading the Mapbox.framework that you copied to
/Library/Frameworks. With an app that you deploy to other users, you'll
want to include Mapbox.framework in your app. Here's how to do that:
- In Finder, control-click on your project's .app bundle and choose
Show Package Contents.
- Create a folder named Frameworks in the Contents folder.
- Copy and paste Mapbox.framework from the mapbox-macos-sdk folder
to your app's Frameworks folder.
- In Lazarus, copy and paste these linker switches into Custom Options in
Project Options:
-k-F"'$ProjPath()$NameOnly($TargetFile()).app/Contents/Frameworks'"
-k-rpath
-k@executable_path/../Frameworks
This will link your app's executable to the app bundle's
Mapbox.framework (see ld)
and indicates where to look for the framework at runtime (see
dyld).
(Note -k-F is the same as FPC's -Ff switch.)
Now compile the app again.
BoxCast LCL: an example app
The source for BoxCast LCL, a more extensive example, is included in the
mapbox-macos-pascal folder. BoxCast LCL is a Lazarus version of the
original Boxcast JavaScript app given
here.
BoxCast LCL uses two additional .lpk packages, as well as the NDFD framework for
retrieving weather forecast data to display on the map. BoxCast LCL also
demonstrates how to do the following:
- Add an event handler for when the map is double-clicked.
- Call a dynamic library (in NDFD.framework), passing the double-clicked
coordinates to retrieve weather forecast data for that location.
- Add a point to the map at the double-clicked coordinates, labeling
it with the retrieved data.
- Save and load app state (map center, zoom level, etc.).
- Display simple help for how to use the map.
- Display the app version from the app's Info.plist file in the About box.
If you want to compile and run BoxCast LCL yourself, here are the steps:
- Edit BoxCast LCL's accesstoken.inc and substitute your own Mapbox access
token.
- Compile LazXProj's appxproj.lpk package in Lazarus, but don't
install it. More information is
here.
- Open LazXProj's fix_xproj.lpi in Lazarus and compile it.
- Open boxcast_lcl.lpi in Lazarus and create the BoxCast LCL.app
bundle (in Project Options).
- Follow steps 1-3 in deploying section above.
- Create NDFD.framework and copy it to BoxCast LCL.app's Frameworks
folder (alongside Mapbox.framework). Instructions for creating
NDFD.framework are here.
- Compile the ndfd_framework_pkg.lpk package in Lazarus, but don't
install it.
- Open boxcast_lcl.lpi in Lazarus and run it.
For other platforms, you can still compile and run this app, although the
map won't display. For non-Mac platforms, skip steps 4-6, but do
create the NDFD library (ndfd.dll or
libndfd64.so) and copy it into the boxcast_lcl directory. With step 8,
be sure to select either the Mac non-64-bit Cocoa or the Non-Mac
build mode in Project Options, then compile and run.
Misc. notes
- What about the possibility of a cross-platform TMbMap? Certainly the
Qt5 widgetset would be a good possibility. You would need a library that
wraps ("flattens") the Mapbox Qt SDK's C++ classes, similar to what
the Qt5Pas library does now for the Qt5 C++ classes (the Qt5Pas library
is used by the LCL Qt5 widgetset). Then you would need to implement
qtmbmap.pas for TMbMap (analogous to cocoambmap.pas). For an example
of a cross-platform custom control that has both Cocoa and Qt
implementations, see TWebBrowser (source is
here).
- A benefit of a cross-platform custom control is how it simplifies and
smooths the use of a disparate, alien library in a different setting,
in this case Lazarus. For example, look at BoxCast LCL's
double-click handler. Nothing could be simpler or more Lazarus friendly:
procedure TMainForm.MapDblClickMap(Sender : TObject;
Latitude : Double;
Longitude : Double);
begin
if not Ndfd.LoadForecast(FloatToStr(Latitude, FmtSettings),
FloatToStr(Longitude, FmtSettings),
False) then
begin
MessageDlg('Unable to retrieve weather forecast data.', mtError, [mbOK], 0);
Exit;
end;
Map.AddPointAnnotation(Latitude, Longitude,
'Today''s forecast',
'High ' + Ndfd.GetMaxTemp(1) +
', Low ' + Ndfd.GetMinTemp(1));
end;
How this is implemented underneath is of little interest to the
user of the custom control. And in fact, the implementation can and will
change as needed, for example to add support for additional platforms.
The big drawback of a custom control, though, is that it
requires an enormous amount of work to wrap an entire library,
involving countless decisions about implementation details and APIs,
all the while trying to preserve the possibility of cross-platform
support.
It's no different with TMbMap and so only a tiny portion of the
underlying Mapbox library is currently wrapped by TMbMap. Part 3
will show how working directly with the Mapbox library in Xcode opens up
all of its functionality, although at the price of cross-platform code.
- Since the Mapbox framework requires at least macOS 10.10, you can add
the -WM10.10 switch in your project's Custom Options. That will
override Lazarus's default of 10.5 for
-macosx_version_min with Cocoa.
You can also set
LSMinimumSystemVersion
in your project's Info.plist file to the same value, like this:
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
- Mapbox caches the imagery (tiles) it downloads. If you need to delete
the cache for some reason (for example, to test how long a map takes to
download), you can delete them. For example, the cached files for
BoxCast LCL are stored in ~/Library/Caches/com.junk.BoxCast-LCL
and ~/Library/Application Support/com.junk.BoxCast-LCL/.mapbox.
As with the app's preferences file name (~/Library/Preferences/com.junk.BoxCast-LCL.plist),
the app's bundle ID is used to name the folders.
On to Part 3 ...
Copyright 2018 by Phil Hess.
macpgmr (at) icloud (dot) com
First posted March 16, 2018.