Skip to content

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

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.

{ 1 } Comments

  1. Borja | April 10, 2014 at 10:36 am | Permalink

    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

Bad Behavior has blocked 4 access attempts in the last 7 days.

Close