Blog

All Blog Posts  |  Next Post  |  Previous Post

Extend TMS WEB Core with JS Libraries with Andrew:
Leaflet

Tuesday, July 19, 2022

Photo of Andrew Simard

There are so many useful JavaScript libraries yet to explore that it can be a challenge to pick what to cover next. But this time out, we're going to have a look at a JavaScript library suggested by blog reader Eddy Poullet, way back in April when this blog series first started. The request? Leaflet, which bills itself as 'a JavaScript library for interactive maps.' And who doesn't like interactive maps? Easily one of the more useful applications to be found on any mobile device. And while there are a number of other ways to get interactive maps into your TMS WEB Core applications, this is a good example where it is nice to have a few choices available. 

Motivation.

Google Maps and Apple Maps tend to get most of the attention, as one of these is likely to be the default when it comes to using maps on any mobile device. And these tend to be very well integrated into their respective devices, naturally, as the developers in these cases are also responsible for their respective operating systems. No surprise there.

Desktop apps, and websites specifically, tend to be more commonly configured to use Google Maps or perhaps MapQuest or less commonly other providers, and far less frequently Apple Maps. Perhaps that will change over time as developers integrate Apple Maps into their websites.  Perhaps we'll cover that another time. But that's historically been one of the troubles of interactive maps - jumping through hoops to get at the data. Whether it is an API key or a developer account or some kind of license or an unhelpful rate limit.  Leaflet offers a bit of a different approach in that there's nothing to do to get started in terms of API keys or developer account registration. Just load up a JavaScript library and be immediately productive.


Getting Started.

As has become custom, we'll start with a new TMS WEB Core project, using the Bootstrap template, to begin with.  And then add the necessary links to the Leaflet JavaScript and CSS files.

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.8.0/dist/leaflet.min.css">
    <script src="https://cdn.jsdelivr.net/npm/leaflet@1.8.0/dist/leaflet.min.js"></script>


In the project itself, add a TWebHTMLDiv to the form and set its Name and ElementID to 'divMap' just so we can reference it. To immediately see Leaflet in action, it just needs to be initialized. This can be done directly from within WebFormCreate like this, taken directly from the excellent documentation in their Quick Start Guide.


procedure TForm1.WebFormCreate(Sender: TObject);
begin
  asm
    var map = L.map('divMap').setView([51.505, -0.09], 13);
    var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
          maxZoom: 19,
          attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
      }).addTo(map);
  end;
end;

Doesn't get much simpler than that. Using the new TWebCSSClass component in combination with Bootstrap, we can add in some rounded corners here and there and immediately get a pretty reasonable interactive map that supports zooming in/out and panning around. 


TMS Software Delphi  Components

Leaflet Up and Running.

The interface that is rendered uses pure HTML/CSS so it is possible to change the look of things like the zoom-in/out controls or other elements. The content (the map itself) consists of a collection of image tiles. Can't do much with those directly in HTML/CSS. But there are a lot of things that can be done with what is being generated inside the tiles.

And speaking of tiles, while everything so far is pretty quick and easy, there are some considerations that come with the set of tiles. In this example, OpenStreetMap is the source of the tile data, and they have their own policies to keep in mind. As with many of these kinds of things, this is a community-supported and shared resource, and as such the limits imposed are generally related to ensuring people don't abuse the service. No downloading all the tiles at once, for example.

Finding Yourself.

Modern browsers typically know where they are in the world, whether it is the browser on your phone or the one on your desk. Permissions are an issue here, so by default browsers should be asking permission before giving up their location to just any old website. Also, if you publish an app, it should be served via HTTPS in order for this built-in geolocation to work. Fortunately, it works fine out of the box when developing the app, so no need to worry about that. Leaflet even has some extra helpers to make this really easy. If we add a button to our app, we can just add a function to implement this entirely. Clicking the button pops up a request to share your location.

procedure TForm1.btnFindMeClick(Sender: TObject);
begin
  asm
    this.map.locate({setView:true});
  end;
end;


And indeed it comes up with a map that contains my exact location (Vancouver, British Columbia, Canada).


TMS Software Delphi  Components

Browser Geolocation Support.

Finding Other Locations.

Easy enough. What about other locations? The process of converting an address or a location into a set of coordinates is referred to as geolocation. And Leaflet doesn't really have any geolocation facilities built into it.  Easily addressed with another JavaScript library, though. We'll use universal-geocoder for this. It works with several providers, but for now, we'll just use it with OpenStreetMap so we can continue to not care about API keys. And this also works directly in the browser, so we don't have to set up any kind of server to protect those non-existent API keys.

  <script src="https://cdn.jsdelivr.net/npm/universal-geocoder@0.14.2/dist/universal-geocoder.min.js"></script>


With the JavaScript library in place, we can add another button and a TWebEdit control to get input from the user.  We'll then pass that over to the library and get back a bunch of information. Mostly what we're after are the latitude and longitude coordinates, so we can update the map.

The other data that is returned is relatively extensive. For example, multiple search results are returned, which could be used to create a dropdown of selection options. Various related region names and codes are returned if you were interested in applying filters. And the search query itself can be extended to include its own filters. For example, limiting searches to a set of countries using the ubiquitous ISO 3166 codes. But a quick and simple search function can be implemented like this.


procedure TForm1.btnSearchClick(Sender: TObject);
var
  search: String;
begin
  search := edtSearch.Text;
  asm
    const openStreetMapGeocoder = UniversalGeocoder.createGeocoder({
      provider: "openstreetmap",
      userAgent: "Leaflet TMS WEB Core Demo"
    });
    openStreetMapGeocoder.geocode(search, (results) => {
      if (results.length > 0) {
        this.map.flyTo([results[0].coordinates.latitude,results[0].coordinates.longitude], 13);
      }
      else {
        alert('No results found');
      }
    });
  end;
end;

procedure TForm1.WebFormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if key = VK_RETURN then btnSearchClick(Sender);
end;


Entering some text and hitting the enter key will result in a little animation, flying you over to your selected destination. Plenty of options are available to control how you get from point A to point B, whether via this little "flyTo" method, via panning, or just simply updating the map with the new location, without animation of any kind. 

TMS Software Delphi  Components

Search for Locations.

Annotations and Overlays.

A large portion of the documentation is devoted to handling annotations and overlays. Adding your own shapes to the map, for example, or even directly overlaying video or SVG content. Weather satellite imagery is used as an example of video content. Shapes can be the usual rectangles and circles, but also any polygon. All of these can be fitted to boundaries on the displayed map so alignment isn't really something to be concerned with. As an example, let's draw a 250m circle around our search results.

procedure TForm1.btnSearchClick(Sender: TObject);
var
  search: String;
begin
  search := edtSearch.Text;
  asm
    const openStreetMapGeocoder = UniversalGeocoder.createGeocoder({
      provider: "openstreetmap",
      userAgent: "Leaflet TMS WEB Core Demo"
    });
    openStreetMapGeocoder.geocode(search, (results) => {
      if (results.length > 0) {
        this.map.flyTo([results[0].coordinates.latitude,results[0].coordinates.longitude], 13);
        L.circle([results[0].coordinates.latitude,results[0].coordinates.longitude],{radius:250}).addTo(this.map);
      }
      else {
        alert('No results found');
      }
    });
  end;
end;


We could also get location bounds from our search results (expressed as corners of a rectangle, which could be converted into a circle/diameter easily enough) and use that to draw a shape instead. But a fixed-size circle looks pretty good here.

TMS Software Delphi  Components

Highlight the Search Results.


There is also support for popups, custom marker overlays, and other annotations.

Event Handling.

Knowing where a user has clicked is handled through the Leaflet event system. Let's say for example you wanted a user to enter a series of points to use as a geofence. We'll need a button to start and stop selecting points, and then create a new polygon based on the points that were selected. For our example, we'll use the VLT (Very Large Telescope) in South America. This is actually a collection of telescopes, but there are four main telescopes that make up the VLT itself. So we'll draw a polygon around the four main telescopes, and then we'll be able to detect whether we've clicked inside this geofence by attaching an event handler to it. Here's what it looks like.


TMS Software Delphi  Components

Event Handling in Leaflet.


We'll set up the Create GeoFence button as a toggle of sorts. Click it to start entering points. And then click it again to make the polygon. Nothing as fancy as editing or canceling points in our example here and the polygon isn't drawn progressively, but the idea here is sound and works pretty well.

procedure TForm1.AddToPolygon(lat: String; lng: String);
begin
  // Building GeoFence
  if btnGeoFence.Tag = 1 then
  begin
    Polygon.Add(',');
    Polygon.Add('['+lat+','+lng+']');
    btnGeoFence.Caption := IntToStr(Polygon.Count div 2)+' point(s)';
  end;
end;

procedure TForm1.btnGeoFenceClick(Sender: TObject);
var
  i: integer;
  strarray:String;
  fencelabel:string;
begin
  if btnGeoFence.Tag = 0 then
  begin
    // Enter "entry mode"
    Fences := Fences + 1;
    btnGeoFence.Tag := 1;
    btnGeoFence.ElementHandle.classList.replace('btn-primary','btn-danger');
  end
  else
  begin

    if (Polygon.Count >= 3) then
    begin

      strarray := '[';
      for i := 1 to Polygon.Count-1 do
      begin
        strarray := strArray+Polygon[i];
      end;
      strarray := strarray+']';
      fencelabel := 'GeoFence #'+IntToStr(Fences);
      asm
        var latlngs = JSON.parse(strarray);
        var geofence = L.polygon(latlngs, {color:"red"}).addTo(this.map);
        geofence.on('click', function(e){ console.log('Clicked on '+fencelabel)});
      end;
    end;

    // Reset back to normal
    btnGeoFence.Tag := 0;
    btnGeoFence.ElementHandle.classList.replace('btn-danger','btn-primary');
    btnGeoFence.Caption := 'Create GeoFence';
    Polygon.Text := '';
  end;
end;

procedure TForm1.WebFormCreate(Sender: TObject);
begin

  // Lets start with the focus here
  edtSearch.setFocus;

  // Polygon user can build to define geofence
  Polygon := TStringList.Create;

  asm
    this.map = L.map('divMap').locate({setView:true});

    var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
          maxZoom: 19,
          attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
      }).addTo(this.map);


    function onMapClick(e) {
      pas.Unit1.Form1.AddToPolygon(e.latlng.lat, e.latlng.lng);
    }
    this.map.on('click', onMapClick);

  end;

end;


Now, the mouse cursor automatically changes to a pointer when over the geofence that is created, and clicking it adds the log entry to the console. More UI could be added to name the geofence, and perhaps a list component to cycle between them or otherwise manage them (delete, edit, etc.).

Other Directions.

For an interactive mapping library, Leaflet is surprisingly easy to set up and works pretty well without having to set up anything like an account or API keys, or anything else. And what we've covered in this post isn't really much more than what is in the "getting started" tutorial. Other tutorials cover topics like GeoJSON, WMS, TMS, and even non-geographical maps. And there's plenty of documentation that describes how it all works. Overall a very nice JavaScript library and a solid addition to any TMS WEB Core project.

Thanks, Eddy!

LeafletExample.zip attached.


Follow Andrew on 𝕏 at @WebCoreAndMore or join our
𝕏
Web Core and More Community.



Andrew Simard




This blog post has received 7 comments.


1. Tuesday, July 19, 2022 at 4:42:01 PM

Very cool!

Price Rhett


2. Tuesday, July 19, 2022 at 5:50:52 PM

Your posts are awesome, I don''t know where to begin starting to use all this information.
Your from Vancouver, Wow!
Great job you are doing here, I know I am going to use as much of these posts in my future projects as I can.
Thanks for all you details & hard work.

Green Lawrence n. Green


3. Tuesday, July 19, 2022 at 8:50:55 PM

Thanks! Leaflet was a particularly fun JS library to work with. Very easy to get started. I''ve not yet used it in a project of my own but I plan to soon. If anyone is interested, the background photo on my donation page (https://www.buymeacoffee.com/andrewsimard500) was taken about 100km north of the VLT when I visited there in 2013. "Hand of the Desert'' is located literally in the middle of nowhere in the Atacama Desert in northern Chile.

Andrew Simard


4. Wednesday, July 20, 2022 at 11:08:31 PM

Yes very cool all your posts.
Your are from Vancouver and one my employe will move with his family in September to Vancouver and I ask me perhaps he could continue to work for us.
The only thing he needs some course for using TMS WEB CORE and perhaps I don''t know if you could do such course.
Vincenzo Sita

Vincenzo Sita


5. Thursday, July 21, 2022 at 6:01:21 AM

Hi there... Thanks for the kind words. I very much like it when people post comments as it makes it feel less like I am talking to myself all the time :-) As for your question about training or a course, there is already plenty of material available online, and more is being added all the time. Taking advantage of those resources first may be more suitable, depending on the situation. If anyone wants to contact me directly to discuss anything in more detail, please send me a direct message within the TMS Support Center.

Andrew Simard


6. Sunday, July 24, 2022 at 1:26:31 PM

Effectively great article. Thank you Andrew for hearing my (small) voice.

Poullet Eddy


7. Monday, July 25, 2022 at 1:59:33 AM

You are most welcome, it was a fantastic suggestion!

Andrew Simard




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post