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:

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.

  1. Sign up for a Mapbox account. This is free to start using.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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:


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:

  1. In Finder, control-click on your project's .app bundle and choose Show Package Contents.

  2. Create a folder named Frameworks in the Contents folder.

  3. Copy and paste Mapbox.framework from the mapbox-macos-sdk folder to your app's Frameworks folder.

  4. 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:

If you want to compile and run BoxCast LCL yourself, here are the steps:

  1. Edit BoxCast LCL's accesstoken.inc and substitute your own Mapbox access token.

  2. Compile LazXProj's appxproj.lpk package in Lazarus, but don't install it. More information is here.

  3. Open LazXProj's fix_xproj.lpi in Lazarus and compile it.

  4. Open boxcast_lcl.lpi in Lazarus and create the BoxCast LCL.app bundle (in Project Options).

  5. Follow steps 1-3 in deploying section above.

  6. Create NDFD.framework and copy it to BoxCast LCL.app's Frameworks folder (alongside Mapbox.framework). Instructions for creating NDFD.framework are here.

  7. Compile the ndfd_framework_pkg.lpk package in Lazarus, but don't install it.

  8. 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

  1. 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).

  2. 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.

  3. 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>
    

  4. 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.