Blog

All Blog Posts  |  Next Post  |  Previous Post

When times are hard, tmssoftware makes developing easy

Wednesday, April 8, 2020

First of all we still hope everyone is well and safe in these uncertain times.
We at tmssoftware are doing the best we can to give you our best support and new features while we are all working from home. As Billy Ocean sang so wisely: "When the going gets tough, the tough get going". And we keep going, we have a new product TMS FNC Maps.
Today I will show you an example of what you can create with some teamwork between TMS FNC Cloud Pack and TMS FNC Maps. TMS FNC Maps is already available for you as a BETA, if you are an active ALL-ACCESS user.
In case you don't have an ALL-ACCESS subscription, we've created a similar web demo on which you can have an initial look.

Our new product: TMS FNC Maps

Experience the power of dynamically switching between various mapping services with this out of the box, worry free experience that provides mapping, directions and geocoding in one big library that works on 4 frameworks and a lot more operating systems.
In case you've missed our announcement, you can find that blogpost here/.

TMS FNC Cloud Pack

TMS FNC Cloud Pack contains components that offer integration with several cloud services. With one source code base to use on multiple frameworks (FMX, VCL, WEB and LCL), this is the best cross-framework option to use your most used REST based services. In case your specific service isn't implemented in the available components, you can easily create your own REST service client components built on top of the TMS FNC Cloud Pack Core.

Easy to develop with

To stay informed is one of the most important things right now, so we wanted to give you a useful project. We will wrap an API, that was built by developer Kyle Redelinghuys, which works with the COVID-19 information sourced from Johns Hopkins CSSE. We combine this with TMS FNC Maps to retrieve the geo location from the countries and draw a circle with a radius in relation to the unfortunate deaths in that country. (Note: This image is from the seventh of April.)



API Wrapper

To start, we will create our own class so we can personalize the object to our preferences. We start from the TTMSFNCSimpleCloudOAuth class and we add a TObjectList Countries that will contain the data for each country.
TTMSFNCCovidAPI = class(TTMSFNCSimpleCloudOAuth)
  private
    FCountries: TTMSFNCCloudCovidCountries;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Countries: TTMSFNCCloudCovidCountries read FCountries;
  end;
We will use the GET Summary call to retrieve the data from all the countries. In the API documentation we can see that we need to send the request to: 'https://api.covid19api.com/summary'. So we will create our API object and make the call.
  covid := TTMSFNCCovidAPI.Create(Self);
  covid.Request.Host := 'https://api.covid19api.com';
  covid.Request.Path := '/summary';
  covid.ExecuteRequest( (*the anonymous method will be explained later*));
To make the code a little more workable, we will put the data from the API in a class and have a separate parsing function. The data that we receive is formatted in the following way:

{
  "Countries": [
    {
      "Country": "Afghanistan",
      "CountrySlug": "afghanistan",
      "NewConfirmed": 2,
      "TotalConfirmed": 24,
      "NewDeaths": 0,
      "TotalDeaths": 0,
      "NewRecovered": 0,
      "TotalRecovered": 1
    },
    {
      "Country": "Albania",
      "CountrySlug": "albania",
      "NewConfirmed": 6,
      "TotalConfirmed": 70,
      "NewDeaths": 0,
      "TotalDeaths": 2,
      "NewRecovered": 0,
      "TotalRecovered": 0
    }
  ]
}
So this is what our class will look like:
TTMSFNCCloudCovidCountry = class
  private
    FCovidAPI: TTMSFNCCovidAPI;
    FTotalConfirmed: integer;
    FNewConfirmed: integer;
    FSlug: string;
    FTotalDeaths: integer;
    FNewDeaths: integer;
    FCountry: string;
    FTotalRecovered: integer;
    FNewRecovered: integer;
    FCoordinate: TTMSFNCMapsCoordinateRec;
  public
    constructor CreateCountry(ACovidAPI: TTMSFNCCovidAPI);
    destructor Destroy; override;
    property Country: string read FCountry;
    property Slug: string read FSlug;
    property NewConfirmed: integer read FNewConfirmed;
    property TotalConfirmed: integer read FTotalConfirmed;
    property NewDeaths: integer read FNewDeaths;
    property TotalDeaths: integer read FTotalDeaths;
    property NewRecovered: integer read FNewRecovered;
    property TotalRecovered: integer read FTotalRecovered;
    property Coordinate: TTMSFNCMapsCoordinateRec read FCoordinate;
  end;

  {$IFDEF WEBLIB}
  TTMSFNCCloudCovidCountries = class(TObjectList)
  private
    function GetItem(Index: Integer): TTMSFNCCloudCovidCountry;
    procedure SetItem(Index: Integer; const Value: TTMSFNCCloudCovidCountry);
  public
    property Items[Index: Integer]: TTMSFNCCloudCovidCountry read GetItem write SetItem; default;
  end;
  {$ELSE}
  TTMSFNCCloudCovidCountries = class(TObjectList<TTMSFNCCloudCovidCountry>);
  {$ENDIF}
As you might notice in the code, we will put these different TTMSFNCCloudCovidCountry items in an TObjectList TTMSFNCCloudCovidCountries . As generics aren't available yet in TMS WEB Core (but being worked on as we speak), we have a small workaround to use them for our cause.
Another thing that might stand out is TTMSFNCMapsCoordinateRec, we will get to that in the TMS FNC Maps part of the demo, but first we will finish our TTMSFNCCovidAPI.

In previous versions we had to parse all of the data ourselves as shown in the following code. But with the new features of TMS FNC Core, this isn't necessary anymore.
procedure TTMSFNCCloudCovidCountry.ParseCountryJSON(AJSON: TJSONValue);
var
  jvv: TJSONValue;
begin
  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'Country');
  if Assigned(jvv) then
    FCountry := Trim(TTMSFNCUtils.GetJSONProp(AJSON, 'Country'));

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'Slug');
  if Assigned(jvv) then
    FSlug := Trim(TTMSFNCUtils.GetJSONProp(AJSON, 'Slug'));

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'NewConfirmed');
  if Assigned(jvv) then
    FNewConfirmed := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'NewConfirmed');

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'TotalConfirmed');
  if Assigned(jvv) then
    FTotalConfirmed := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'TotalConfirmed');

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'NewDeaths');
  if Assigned(jvv) then
    FNewDeaths := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'NewDeaths');

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'TotalDeaths');
  if Assigned(jvv) then
    FTotalDeaths := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'TotalDeaths');

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'NewRecovered');
  if Assigned(jvv) then
    FNewRecovered := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'NewRecovered');

  jvv := TTMSFNCUtils.GetJSONValue(AJSON, 'TotalRecovered');
  if Assigned(jvv) then
    FTotalRecovered := TTMSFNCUtils.GetJSONIntegerValue(AJSON, 'TotalRecovered');
end;
All of the previous code can be replaced with the ToJSON function. Ths is another example of how tmssoftware keeps on making things easier for you.

Now all we need is the rest of our ExecuteRequest and all of the data is handled. The complete function looks like this:
covid.ExecuteRequest(
  procedure(const AResult: TTMSFNCCloudBaseRequestResult)
  var
    I: Integer;
  begin
    o := TTMSFNCUtils.ParseJSON(AResult.ResultString);
    if Assigned(o) then
    begin
      try
        jv := TTMSFNCUtils.GetJSONValue(o, 'Countries');
        if Assigned(jv) then
        begin
          ja := TTMSFNCUtils.GetJSONValue(o, 'Countries') as TJSONArray;

          for I := 0 to TTMSFNCUtils.GetJSONArraySize(ja) - 1 do
          begin
            fo := TTMSFNCUtils.GetJSONArrayItem(ja, i);
            if Assigned(TTMSFNCUtils.GetJSONValue(fo, 'Country')) and (TTMSFNCUtils.GetJSONProp(fo, 'Country') <> '') then
            begin
              country := TTMSFNCCloudCovidCountry.CreateCountry(covid);
              
              country.JSON := fo.ToJSON;

              covid.FCountries.Add(country);

              if country.TotalDeaths > 0 then
              begin
                g.GetGeocoding(country.Country,
                procedure(const ARequest: TTMSFNCGeocodingRequest; const ARequestResult: TTMSFNCCloudBaseRequestResult)
                var
                  c: TTMSFNCCloudCovidCountry;
                begin
                  c := TTMSFNCCloudCovidCountry(ARequest.DataPointer);
                  c.FCoordinate := ARequest.Items[0].Coordinate.ToRec;
                end, '', country);
              end;
            end;
          end;
        end;
      finally
        o.Free
      end;
    end;
  end
  );

This might look like a lot, but I will explain the code bit by bit. First of all we define our Anonymous method that will handle the request result, which we parse to JSON.
Then we check if the response has the value 'Countries', otherwise something went wrong with our request. As this is an array of all of the different countries, we will go over them one by one. The check to see if the JSON property for Country is different from an empty string is because we noticed that there was an empty object in the response. But if the Country has a name, then we create a new Country object.
We parse the information in the object and add it to the object list.

A first hand on TMSFNCMaps

For the last part we will move over to TMS FNC Maps. ‘g’ is an instance of the type TTMSFNCGeocoding. We create this in the FormCreate and add an event to OnRequestsComplete. TTMSFNCGeocoding is a component to perform geocoding of an address or reverse geocoding of a coordinate by using an existing REST API service.
g := TTMSFNCGeocoding.Create(Self);
g.OnRequestsComplete := DoRequestsComplete;
g.APIKey := TMSFNCMaps1.APIKey;
We created the TMSFNCMaps control in design-time, where you can also set the API key. Or this can be done in the code with the property TMSFNCMaps1.APIKey.



To get back to the Geocoding in our API response. The call for geocoding is as followed:
TTMSFNCCustomGeocoding.GetGeocoding(AAddress: string; ACallback: TTMSFNCGeocodingGetGeocodingCallBack = nil; AID: string = ''; ADataPointer: Pointer = nil);
AAddress: is the country name which we placed in the string Country.country. ACallback: is the anonymous method that we use to store the coordinates in c.FCoordinate. AID: is an empty string as we don’t use it. ADataPointer: is the country that we just added and that we convert to c.

That is how we retrieve the coordinates for the countries by their name.

Lets add some things to the map as we would like to have a visible indication to show the impact of the virus, therefor we draw circles on the locations and color them.
This is extremely simple in TTMSFNCMaps with the AddCircle method where you just need to give the circle center and the radius.
We do this in the DoRequestsComplete event that is triggered when our geo location is done.
procedure TForm1.DoRequestsComplete(Sender: TObject);
var
  I: Integer;
  cl: TTMSFNCGraphicsColor;
begin
  TMSFNCMaps1.BeginUpdate;
  for I := 0 to covid.Countries.Count - 1 do
  begin
    with TMSFNCMaps1.AddCircle(covid.Countries[I].Coordinate, Max(50000, 25 * covid.Countries[I].TotalDeaths)) do
    begin
      DataPointer := covid.Countries[I];
      cl := gcSeagreen;
      if covid.Countries[I].TotalDeaths > 500 then
        cl := gcOrange;
      if covid.Countries[I].TotalDeaths > 1000 then
        cl := gcRed;

      FillColor := cl;
      Strokecolor := cl;
      FillOpacity := 0.5;
    end;
  end;

  TMSFNCMaps1.EndUpdate;
end;

The last thing we will add in this example are some events of TTMSFNCMaps. To be more precise we will show a pop-up when we hover the circles that will show the total numbers we retrieved from the API. We do this in the event OnPolyElementMouseEnter.
With the country that was set as datapointer in DoRequestsComplete, we can easily get the different data in the event.
We just call the ShowPopUp method with the location and the text we want to show and as you can see in the example we can use HTML for this as well.
procedure TForm1.TMSFNCMaps1PolyElementMouseEnter(Sender: TObject;
  AEventData: TTMSFNCMapsEventData);
var
  c: TTMSFNCCloudCovidCountry;
begin
  if Assigned(AEventData) then
  begin
    c := AEventData.PolyElement.DataPointer;
    TMSFNCMaps1.ShowPopup((AEventData.PolyElement as TTMSFNCMapsCircle).Center.ToRec,
'
' + c.Country + '

Contaminations: ' + c.TotalConfirmed.ToString + '
Deaths: ' + c.TotalDeaths.ToString + '
Recovered: ' + c.TotalRecovered.ToString + '
', 0, 0); end; end; procedure TForm1.TMSFNCMaps1PolyElementMouseLeave(Sender: TObject; AEventData: TTMSFNCMapsEventData); begin TMSFNCMaps1.CloseAllPopups; end;
And that’s all folks, in less than 250 lines of code, we’ve created a very appealing application that retrieves data from an API and shows different things on a map.
We didn’t use the most valuable feature of TMSFNCMaps, but in case Google Maps is down, we can easily change for another service like Bing, Azure, Here, TomTom, MapBox or OpenLayers to keep the application up-and-running.

This example shows how easy it is to use two very powerful TMS FNC products. And we will go on and try to amaze you with new products and features that are very user-friendly.

Stay safe and remember: “When the going gets rough, the tough get rough”.

Gjalt Vanhouwaert




This blog post has received 2 comments.


1. Thursday, April 9, 2020 at 3:34:50 AM

Very nice! Thanks everyone for the hard work! Stay safe :)

Price Rhett


2. Thursday, April 9, 2020 at 8:10:47 AM

Well done. Am very pleased to see that development is still continuing in spite of this current crisis!

Randall Ken




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