Planning a vacation? Do it mobile REST Client style using C++Builder and Delphi

Posted by on in Blogs
I am planning a vacation during my wife's winter break. To remind me of the number of days until I leave and to be able to see what's happening with the weather, I've built an iOS mobile application using C++Builder and Delphi (I finished the code for both versions and posted this blog while I was watching Seattle overwhelm Denver in the SuperBowl).



The UI contains a TTabControl to contain three tabs for Weather, Browser and Settings. The app uses TGeocoder to get the latitude and longitude for an address. The app uses TWebBrowser to use Google Maps to display my vacation spot. I use TCalendarEdit to select the vacation date using the native date picker for iOS. Finally, the app uses the REST Client library components to access the WeatherBug weather forecast JSOn api to display the next seven days of weather.

Here are the three TabItems in my mobile application:



The first thing the code does, using the form's OnCreate event, is initialize some variables, set up the Geocoder and load the WeatherBug API key from an INI file. I used the Project | Deployment menu to add the "weather.ini" file to my deployment and place it in the "StartUp\Documents" folder for iOS (C++ and Delphi) and in ".\assets\internal" for Android (Delphi). [note: I place the INI file in the "c:\temp" folder on Windows - see the code for the OnCreate event handler]. Here is the INI file (minus my WeatherBug API key - you can get your own key by following the steps at http://developer.weatherbug.com/page):
weatherbug.ini:

[WeatherBug]
APIKey=xxxxx

Here is the Delphi and C++ form OnCreate code:
C++:

void __fastcall TForm6::FormCreate(TObject *Sender)
{
TIniFile *Ini;
// set up for Geocode
FGeocoder = (TGeocoder*)new TGeocoderClass(TGeocoder::Current);
FGeocoder-&gt:OnGeocode = OnGeocodeEvent;
VacationTabControl-&gt:ActiveTab = WeatherTabItem;
FoundLatitude = 0.0;
FoundLongitude = 0.0;
FoundAddrLatLong = false;
RefreshButton-&gt:Visible = false;
// read WeatherBug API Key from INI file
#if defined(TARGET_OS_IPHONE) || defined(TARGET_OS_MAC)
Ini = new TIniFile(System::Ioutils::TPath::GetDocumentsPath() + PathDelim + "weather.ini");
#else
Ini = new TIniFile("c:\\temp\\weather.ini");
#endif
APIKeyString = Ini-&gt:ReadString("WeatherBug","APIKey","");
}

Delphi:

procedure TForm5.FormCreate(Sender: TObject);
var
Ini: TIniFile;
begin
// set up for Geocode
FGeocoder := TGeocoder.Current.Create;
FGeocoder.OnGeocode := OnGeocodeEvent;
VacationTabControl.ActiveTab := WeatherTabItem;
FoundLatitude := 0.0;
FoundLongitude := 0.0;
FoundAddrLatLong := false;
RefreshButton.Visible := false;
// read WeatherBug API Key from INI file
{$IF DEFINED(IOS) or DEFINED(ANDROID) or DEFINED(MACOS)}
Ini := TIniFile.Create(TPath.GetDocumentsPath + PathDelim + 'weather.ini');
{$ELSE}
Ini := TIniFile.Create('c:\temp\weather.ini');
{$ENDIF}
APIKeyString := Ini.ReadString('WeatherBug','APIKey','');
end;

The following bitmap shows the Project | Deployment entry for the "weather.ini" file showing it being deployed to the right folders on iOS (StartUp\Documents) and Android (.\assets\internal).



On the Settings TabItem, you can select your vacation date and address. When the address is changed, the OnChange event handler fires, stores the address in the TCivicAddress class and calls Geocode to get the new Latitude and Longitude. TGeocoder also has a GeocodeReverse method and OnGeocodeReverse event that takes latitude/longitude and returns TCivicAddress information. You can find more information and a tutorial example at http://docwiki.embarcadero.com/RADStudio/XE5/en/Mobile_Tutorial:_Using_Location_Sensors_(iOS_and_Android)). Here is the code for the AddressEditChange event.
C++:

void __fastcall TForm6::AddressEditChange(TObject *Sender)
{
// Address changed - get Latitude/Longitude via GeoCoding
RefreshButton->Visible = false;
// use address to find Latitude and Longitude
lAddress = new TCivicAddress;
try {
lAddress->Address = AddressEdit->Text;
FGeocoder->Geocode(lAddress);
}
__finally {
delete lAddress;
};
}

Delphi:

procedure TForm5.AddressEditChange(Sender: TObject);
begin
// Address changed - get Latitude/Longitude via GeoCoding
RefreshButton.Visible := false;
// use address to find Latitude and Longitude
lAddress := TCivicAddress.Create;
try
lAddress.Address := AddressEdit.Text;
FGeocoder.Geocode(lAddress);
finally
lAddress.Free;
end;
end;

When Geocode is called and the geocoding completes, the OnGeocodeEvent fires to get the latitude and longitude. If the address is found and returns the coordinates I display them on the settings page and also call the Navigate method of the TWebBrowser to load a Google Map on the Browser TabItem. Here is the code for the OnGeocodeEvent:
C++:

void __fastcall TForm6::OnGeocodeEvent(System::DynamicArray Coords)
{
if (Coords[0].Latitude!= 0 && Coords[0].Longitude != 0) {
FoundLatitude = Coords[0].Latitude;
FoundLongitude = Coords[0].Longitude;
FoundAddrLatLong = true;
LatLongLabel->Text =
FloatToStr(Coords[0].Latitude)
+ ","
+ FloatToStr(Coords[0].Longitude);
#if defined(TARGET_OS_IPHONE)
FormatSettings.DecimalSeparator = '.';
String URLString = "";
URLString = URLString.sprintf(
L"https://maps.google.com/maps?q=%2.6f,%2.6f",
Coords[0].Latitude, Coords[0].Longitude);
// FormatSettings.DecimalSeparator = LDecSeparator;
WebBrowser1->Navigate(URLString);
#endif
RefreshButton->Visible = true;
}
else {
FoundAddrLatLong = false;
LatLongLabel->Text = "Address not Found!";
}
}

Delphi:

procedure TForm5.OnGeocodeEvent(const Coords: TArray);
begin
if Length(Coords) > 0 then begin
FoundLatitude := Coords[0].Latitude;
FoundLongitude := Coords[0].Longitude;
FoundAddrLatLong := true;
LatLongLabel.Text :=
Format('%3.5f/%3.5f',[Coords[0].Latitude, Coords[0].Longitude]);
{$IF DEFINED(IOS) or DEFINED(ANDROID)}
FormatSettings.DecimalSeparator := '.';
WebBrowser1.Navigate(Format(LGoogleMapsURL, [FoundLatitude.ToString, FoundLongitude.ToString]));
{$ENDIF}
RefreshButton.Visible := true;
end
else begin
FoundAddrLatLong := false;
LatLongLabel.Text := 'Address not Found!'
end;
end;

To finish the coding, my app has a TActionList component and I created an action, MyAction. The action executes when the RefreshButton is clicked. The RESTRequest is executed to call the WeatherBug weather REST GetForecast API and the RESTResponse gets the JSON result. The RESTRequestDataSetAdapter converts the returned JSON and populates the ClientDataSet with the 7 days of weather forecast. The code fills in the WeatherListBox by iterating through the ClientDataSet rows. Here are bitmaps of the REST Client components and their properties in the Object Inspector. I've circled in red the properties most used to process the API call and return the results to the ClientDataSet.





Each ListBox item contains the high and low temperatures and the weather description for each day. Note: if you build the application for Mac OS X, you'll need to use Project | Deployment, click the "Add Feature Files" icon and make sure "Midas Library" is checked. Here is the code for MyAction:
C++:

void __fastcall TForm6::MyActionExecute(TObject *Sender)
{
VacationTabControl->ActiveTab = WeatherTabItem;
TListBoxItem * MyListBoxItem;

// days to vacation
CountdownLabel->Text =
"Days to Vacation: "
+ IntToStr(DaysBetween(
Now(),
CalendarEdit1->Date)
);

if (FoundAddrLatLong) {
// uses WeatherBug REST API
// http://developer.weatherbug.com/docs/read/WeatherBug_Rest_XML_API
// update RESTRequest Resource property
// with latitude, longitude and API key
RESTRequest1->Resource =
"REST/Direct/GetForecast.ashx?la="
+ FloatToStr(FoundLatitude)
+ "&lo="
+ FloatToStr(FoundLongitude)
+ "&ht=t&ht=i&ht=d&api_key="
+ APIKeyString;

// get weather temperatures
RESTRequest1->Execute();

// Populate listbox with temperatures for next 7 days
// TODO: get day and night icons for condition codes
// For now just display strings
WeatherListBox->Items->Clear();
WeatherListBox->BeginUpdate();
ClientDataSet1->First();
while (!ClientDataSet1->Eof) {
MyListBoxItem = new TListBoxItem(WeatherListBox);
MyListBoxItem->Text =
ClientDataSet1->FieldByName("dayTitle")->AsString
+ ": High: "
+ ClientDataSet1->FieldByName("high")->AsString
+ ", Low: "
+ ClientDataSet1->FieldByName("low")->AsString
+ " "
+ ClientDataSet1->FieldByName("dayDesc")->AsString;
WeatherListBox->AddObject(MyListBoxItem);
ClientDataSet1->Next();
}
WeatherListBox->EndUpdate();
}
}

Delphi:

procedure TForm5.MyActionExecute(Sender: TObject);
var
MyListBoxItem : TListBoxItem;
begin
// days to vacation
CountdownLabel.Text :=
'Days to Vacation: '
+ IntToStr(DaysBetween(
Now(),
CalendarEdit1.Date)
)
;

if FoundAddrLatLong then begin

// uses WeatherBug REST API
// http://developer.weatherbug.com/docs/read/WeatherBug_Rest_XML_API
// update RESTRequest Resource property
// with latitude, longitude and API key
RESTRequest1.Resource :=
'REST/Direct/GetForecast.ashx?'
+ 'la='+FoundLatitude.ToString
+ '&'
+ 'lo='+FoundLongitude.ToString
+ '&ht=t&ht=i&ht=d&'
+ 'api_key='+APIKeyString
;

// get weather temperatures
RestRequest1.Execute;

// Populate listbox with temperatures for next 7 days
WeatherListBox.Items.Clear;
WeatherListBox.BeginUpdate;
ClientDataSet1.First;
while not ClientDataSet1.Eof do begin

// TODO: get day and night icons for condition codes
// For now just display strings

MyListBoxItem := TListBoxItem.Create(WeatherListBox);
MyListBoxItem.Text :=
copy(ClientDataSet1.FieldByName('dayTitle').AsString,1,3)
+ ' Hi: '
+ ClientDataSet1.FieldByName('high').AsString
+ ' Lo: '
+ ClientDataSet1.FieldByName('low').AsString
+ ' '
+ ClientDataSet1.FieldByName('dayDesc').AsString
;
MyListBoxItem.TextAlign := TTextAlign.taCenter;
WeatherListBox.AddObject(MyListBoxItem);
ClientDataSet1.Next
end;
WeatherListBox.EndUpdate
end
end;

That's all of the code. Here are the live screens for the Delphi and C++ apps running on my iPhone 4S. The Delphi app also runs on my Samsung Galaxy S4. The scresn show: 1) Settings tab with the DatePicker, 2) Settings tab with the Address and latitude/longitude returned from the Geocoder, 3) Browser tab showing the Google Map and 4) Weather tab showing 7 days of weather forecast via the WeatherBug JSON API.





If you want to use other weather related APIs, there is a good blog post on ProgrammableWeb, "5 Weather APIs – From WeatherBug to Weather Channel", at http://blog.programmableweb.com/2009/04/15/5-weather-apis-from-weatherbug-to-weather-channel/. There are additional weather API sites at World Weather Online and ForeCast.io.

You can find a zip file containing the C++ and Delphi projects on Code Central at http://cc.embarcadero.com/item/29713.
About
Gold User, Rank: 1, Points: 2466
David Intersimone (known to many as David I.) is a passionate and innovative software industry veteran-often referred to as a developer icon-who extols and educates the world on Embarcadero developer tools. He shares his visions as an active member of the industry speaking circuit and is tapped as an expert source by the media. He is a long-standing champion of architects, developers and database professionals and works to ensure that their needs are folded into Embarcadero's strategic product plans. David holds a bachelor's degree in computer science from California Polytechnic State University at San Luis Obispo, California.

Comments

  • Guest
    Borja Thursday, 10 April 2014

    The URL for Google Maps has to be changed. You have to remove "&output=embed" to make it work. On the other hand, the correct URL to get a developer API key is: http://developer.ensb.us/member/register

    Hope it helps to people who find this wonderful sample

  • Please login first in order for you to submit comments
  • Page :
  • 1