Blog

How to Add Geopoints With Related Data Objects

by on September 1, 2019

In another article, we wrote about how to save Backendless data objects with related geopoint(s). The data-to-geo relations are bidirectional. That means that just as a data object can reference a geopoint (or more than one) as a relation, a geopoint may reference a data object or a collection of in its metadata as well.

Consider the example below. The code saves a geopoint which represents the Eiffel Tower. The geopoint references a data object which represents the tower’s main engineer, Gustavo Eiffel, saved in the Architect table. Notice the “gustavoEiffel” object is a plain Java object (a POJO) which is saved in the Data Service storage in Backendless. The relation between the geopoint and the data object is handled through the geopoint’s metadata:

    Architect gustaveEiffel = new Architect();
    gustaveEiffel.setName("Gustave Eiffel");
    
    Calendar calendar = Calendar.getInstance();
    calendar.set(1923, 11, 27);
    gustaveEiffel.setBirthday(calendar.getTime());
    gustaveEiffel.setNationality("French");
    
    Backendless.Data.of(Architect.class).save(gustaveEiffel, new AsyncCallback<Architect>() {
    
       @Override
       public void handleResponse(Architect savedArchitect) {
           GeoPoint eiffelTower = new GeoPoint(48.85815, 2.29452);
           eiffelTower.addCategory("towers");
           eiffelTower.addCategory("placesToVisit");
           eiffelTower.addMetadata("name", "Eiffel Tower");
           eiffelTower.addMetadata("architect", savedArchitect);
    
           Backendless.Geo.savePoint(eiffelTower, new AsyncCallback<GeoPoint>() {
    
               @Override
               public void handleResponse(GeoPoint geoPoint) {
                   Log.i(TAG, "Geo point has been saved - " + geoPoint.getObjectId());
               }
    
               @Override
               public void handleFault(BackendlessFault fault) {
                   Log.i(TAG, fault.getMessage());
               }
           });
       }
    
       @Override
       public void handleFault(BackendlessFault fault) {
           Log.e(TAG, fault.getMessage());
       }
    });

    val gustaveEiffel = Architect()
    gustaveEiffel.name = "Gustave Eiffel"
    
    val calendar = Calendar.getInstance()
    calendar.set(1923, 11, 27)
    gustaveEiffel.birthday = calendar.time
    gustaveEiffel.nationality = "French"
    
    Backendless.Data.of(Architect::class.java).save(gustaveEiffel, object : AsyncCallback<Architect> {
    
       override fun handleResponse(savedArchitect: Architect) {
           val eiffelTower = GeoPoint(48.85815, 2.29452)
           eiffelTower.addCategory("towers")
           eiffelTower.addCategory("placesToVisit")
           eiffelTower.addMetadata("name", "Eiffel Tower")
           eiffelTower.addMetadata("architect", savedArchitect)
    
           Backendless.Geo.savePoint(eiffelTower, object : AsyncCallback<GeoPoint> {
    
               override fun handleResponse(geoPoint: GeoPoint) {
                   Log.i(TAG, "Geo point has been saved - ${geoPoint.objectId}")
               }
    
               override fun handleFault(fault: BackendlessFault) {
                   Log.i(TAG, fault.message)
               }
           })
       }
    
       override fun handleFault(fault: BackendlessFault) {
           Log.e(TAG, fault.message)
       }
    })

    NSString *birthdateString = @"15/12/1832";
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    dateFormatter.dateFormat = @"dd/MM/yyyy";
        
    Architect *gustaveEiffel = [Architect new];
    gustaveEiffel.name = @"Gustave Eiffel";
    gustaveEiffel.nationality = @"French";
    gustaveEiffel.birthdate = [dateFormatter dateFromString:birthdateString];
        
    [[Backendless.shared.data of:[Architect class]] saveWithEntity:gustaveEiffel responseHandler:^(Architect * savedArchitect) {
        NSDictionary *metadata = @{@"name": @"Eiffel Tower", @"architect": savedArchitect};
        GeoPoint *eiffelTower = [[GeoPoint alloc] initWithLatitude:48.85815 longitude:2.29452 categories:@[@"towers", @"placesToVisit"] metadata:metadata];
        [Backendless.shared.geo saveGeoPointWithGeoPoint:eiffelTower responseHandler:^(GeoPoint *savedPoint) {
            NSLog(@"Geo point has been saved - %@", savedPoint.objectId);
        } errorHandler:^(Fault *fault) {
            NSLog(@"Error: %@", fault.message);
        }];
            
    } errorHandler:^(Fault *fault) {
        NSLog(@"Error: %@", fault.message);
    }];

    let birthdateString = "15/12/1832"
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "dd/MM/yyyy"
            
    let gustaveEiffel = Architect()
    gustaveEiffel.name = "Gustave Eiffel"
    gustaveEiffel.nationality = "French"
    gustaveEiffel.birthdate = dateFormatter.date(from: birthdateString)
            
    Backendless.shared.data.of(Architect.self).save(entity: gustaveEiffel, responseHandler: { savedArchitect in
        let metadata = ["name": "Eiffel Tower", "architect": savedArchitect]
        let eiffelTower = GeoPoint(latitude: 48.85815, longitude: 2.29452, categories: ["towers", "placesToVisit"], metadata: metadata)
        Backendless.shared.geo.saveGeoPoint(geoPoint: eiffelTower, responseHandler: { savedPoint in
            print("Geo point has been saved - \(savedPoint.objectId ?? "")")
        }, errorHandler: { fault in
            print("Error: \(fault.message ?? "")")
        })
    }, errorHandler: { fault in
        print("Error: \(fault.message ?? "")")
    })

    const Backendless = require('backendless')
    /*
     Or use `import Backendless from 'backendless'` for client side.
     If you don't use npm or yarn to install modules, you can add the following line
     <script src="//api.backendless.com/sdk/js/latest/backendless.min.js"></script>
     to your index.html file and use the global Backendless variable.
    */
    
    Backendless.initApp('YOUR_APP_ID', 'YOUR_JS_API_KEY')
    
    const saveArchitect = async () => {
      const architect = new Architect()
    
      architect.setName('Gustave Eiffel')
      architect.setBirthday(new Date('05/12/1980').getTime())
      architect.setNationality('French')
    
      const eiffelTower = new Backendless.GeoPoint()
    
      eiffelTower.latitude = 48.85815
      eiffelTower.longitude = 2.29452
      eiffelTower.categories = ['towers', 'placesToVisit']
      eiffelTower.metadata = {
        name     : 'Eiffel Tower',
        architect: architect
      }
    
      const savedPoint = await Backendless.Geo.savePoint(eiffelTower)
    
      console.log(`Geo point has been saved - ${ savedPoint.objectId }`)
    }
    
    const onError = error => {
      console.error('Server reported an error: ', error.message)
      console.error('error code: ', error.code)
      console.error('http status: ', error.status)
    }
    
    Promise.resolve()
      .then(saveArchitect)
      .catch(onError)
    

    Map gustaveEiffel = {
     "name": "Gustave Eiffel",
     "birthday": DateTime(1923, 11, 27),
     "nationality": "French"
    };
    
    Backendless.Data.of("Architect").save(gustaveEiffel).then((savedArchitect) {
     GeoPoint eiffelTower = GeoPoint.fromLatLng(48.85815, 2.29452);
     eiffelTower.categories = ["towers", "placesToVisit"];
     eiffelTower.metadata = {"name": "Eiffel Tower", "architect": savedArchitect};
    
     Backendless.Geo.savePoint(eiffelTower).then(
       (geoPoint) => print("Geo point has been saved - ${geoPoint.objectId}"));
    });

    The Architect class:

      public class Architect {
         private String nationality;
         private String name;
         private Date birthday;
         private String objectId;
      
         public void setName(String name) {
             this.name = name;
         }
      
         public String getName() {
             return name;
         }
      
         public void setBirthday(Date birthday) {
             this.birthday = birthday;
         }
      
         public Date getBirthday() {
             return birthday;
         }
      
         public void setNationality(String nationality) {
             this.nationality = nationality;
         }
      
         public String getNationality() {
             return nationality;
         }
      
         public String getObjectId() {
             return objectId;
         }
      
         public void setObjectId(String objectId) {
             this.objectId = objectId;
         }
      }

      import java.util.Date
      
      data class Architect(var objectId: String? = null, var birthday: Date? = null,
         var name: String? = null, var nationality: String? = null)

      @interface Architect : NSObject
      
      @property (strong, nonatomic) NSString *name;
      @property (strong, nonatomic) NSString *nationality;
      @property (strong, nonatomic) NSDate *birthdate;
      
      @end

      @objcMembers class Architect: NSObject {
          var name: String?
          var nationality: String?
          var birthdate: Date?
      }

      function Architect() {
        let nationality = ''
        let name = ''
        let birthday
      
        this.___class = 'Architect'
      
        this.setName = function (name) {
          this.name = name
      
          return this
        }
      
        this.getName = function () {
          return this.name
        }
      
        this.setBirthday = function (birthday) {
          this.birthday = birthday
      
          return this
        }
      
        this.getBirthdat = function () {
          return this.birthday
        }
      
        this.setNationality = function (nationality) {
          this.nationality = nationality
      
          return this
        }
      
        this.getNationality = function () {
          return this.nationality
        }
      }

      The Backendless SDK for Flutter uses the map/dictionary-based approach for data persistence.

      Once you run the code and open the Geolocation screen in Backendless Console, you will see the following:

      If you click the Architect object link in the geopoint’s metadata column, the Console displays the following popup which makes it very easy to see the related data object:

      Alternatively, if you navigate to the Data screen and select the Architect table, it will show the saved data object:

      Enjoy!

      Leave a Reply