Skip to content

9 Geo based Search Implementation II

9 Geo-based Search Implementation II.md

Google GeoCoding API

The Google Geolocation API provides the function to return the geographic coordinates (e.g. latitude and longitude) of a given address. The official website is at: https://developers.google.com/maps/documentation/geocoding/start

Enable Google GeoCoding API

  1. Go back to the GCP home page at https://console.cloud.google.com and open the navigation menu.
Fine

Screenshot 2024-07-28 at 15.49.45

Screenshot 2024-07-28 at 18.20.47

  1. Under the navigation menu, click Dashboard under APIs & Services.

Screenshot 2024-07-28 at 18.45.05

  1. Click the ENABLE APIS AND SERVICES button to enable the Geolocation API

Screenshot 2024-07-28 at 18.46.32

Screenshot 2024-07-28 at 18.46.54

  1. Search for Geocoding API and click the API from the result.

Screenshot 2024-07-28 at 18.47.19

  1. Click Enable and make sure the GeoCoding API is under the Enabled API list.

Screenshot 2024-07-28 at 18.47.37

Screenshot 2024-07-28 at 18.48.14

  1. Search for Geolocation API and click the API from the result.Screenshot 2024-07-28 at 18.45.52

Screenshot 2024-07-28 at 18.50.41

  1. Click Enable and make sure the Geolocation API is under the Enabled API list

Screenshot 2024-07-28 at 18.51.15

  1. To use the GeoCoding API from your code, we need to create an API key and use it as the authentication credential. Open the navigation menu under the Google Map Platform and click Credentials.

Screenshot 2024-07-28 at 18.51.54

  1. Click CREATE CREDENTIALS button and choose API key.

Screenshot 2024-07-28 at 18.52.16

  1. Make sure the API key is created and we’ll use the key material later in the code. You can click close in the popup window for now. We don’t need to worry about the restriction because you’re not going to share your key with anyone else.

    Screenshot 2024-07-28 at 19.26.50

Integrate Geolocation API with Staybooking Backend

  1. Open your project in Intellij and find the pom.xml file. Add the following dependency.
     <dependencies>

  <!-- ... existing dependencies -->

  <!-- Only insert the following, do not change or touch other lines -->
    <dependency>
        <groupId>com.google.maps</groupId>
        <artifactId>google-maps-services</artifactId>
        <version>2.2.0</version>
    </dependency>
  <!-- Only insert the above, do not change or touch other lines -->
</dependencies>
  1. Open your staybooking project in your IDE, go to the application.properties file and add the API key material you just created.
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.url = jdbc:mysql://localhost:3306/staybooking?createDatabaseIfNotExist=true&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = Eve123456
jwt.secret=secret
gcs.bucket=staybookingevebucket
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
elasticsearch.address=34.68.147.255:9200

elasticsearch.username=eve
elasticsearch.password=12345678
geocoding.apikey=AIzaSyDDxS9pRX4N99o9V56LCei9l7m9f2di4KA
  1. Go to the com.eve.staybooking.config package, create GoogleGeoCodingConfig class to provide GeoApiContext.
package com.eve.staybooking.config;

import com.google.maps.GeoApiContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GoogleGeoCodingConfig {

    @Value("${geocoding.apikey}")
    private String apiKey;

    @Bean
    public GeoApiContext geoApiContext() {
        return new GeoApiContext.Builder().apiKey(apiKey).build();
    }
}
  1. Go to the com.eve.staybooking.exception package, create GeoCodingException which will be thrown if there’s any exception when we connect to Geolocation API.
package com.eve.staybooking.exception;

public class GeoCodingException extends RuntimeException {
    public GeoCodingException(String message) {
        super(message);
    }
}
  1. Under the same package, create InvalidStayAddressException which will be thrown when the given stay address is invalid.
package com.eve.staybooking.exception;

public class InvalidStayAddressException extends RuntimeException {
    public InvalidStayAddressException(String message) {
        super(message);
    }
}
  1. Go to the com.eve.staybooking.service package, create a new class called GeoCodingService.
package com.eve.staybooking.service;

import org.springframework.stereotype.Service;

@Service
public class GeoCodingService {

}
  1. Implement the getLatLong method based on the GeoCoding API.
package com.eve.staybooking.service;

import com.eve.staybooking.exception.GeoCodingException;
import com.eve.staybooking.exception.InvalidStayAddressException;
import com.eve.staybooking.model.Location;

import com.google.maps.GeoApiContext;
import com.google.maps.GeocodingApi;
import com.google.maps.errors.ApiException;
import com.google.maps.model.GeocodingResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.stereotype.Service;

import java.io.IOException;


@Service
public class GeoCodingService {
    private GeoApiContext context;

    @Autowired
    public GeoCodingService(GeoApiContext context) {
        this.context = context;
    }

    public Location getLatLng(Long id, String address) throws GeoCodingException {
        try {
            GeocodingResult result = GeocodingApi.geocode(context, address).await()[0];

            if (result.partialMatch) {
                throw new InvalidStayAddressException("Failed to find stay address");
            }
            return new Location(id, new GeoPoint(result.geometry.location.lat, result.geometry.location.lng));
        } catch (IOException | ApiException | InterruptedException e) {
            e.printStackTrace();
            throw new GeoCodingException("Failed to encode stay address");
        }
    }


}
  1. Update the StayService to save location information to Elasticsearch.
package com.eve.staybooking.service;


import com.eve.staybooking.exception.StayNotExistException;
import com.eve.staybooking.model.*;
import com.eve.staybooking.repository.LocationRepository;

import com.eve.staybooking.repository.StayRepository;
import com.eve.staybooking.repository.StayReservationDateRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class StayService {
    private StayRepository stayRepository;
    private LocationRepository locationRepository;


    private ImageStorageService imageStorageService;
    private GeoCodingService geoCodingService;


    @Autowired
    public StayService(StayRepository stayRepository, LocationRepository locationRepository,
                       ImageStorageService imageStorageService, GeoCodingService geoCodingService
    ) {
        this.stayRepository = stayRepository;
        this.locationRepository = locationRepository;
        this.imageStorageService = imageStorageService;
        this.geoCodingService = geoCodingService;

    }



    public List<Stay> listByUser(String username) {
        return stayRepository.findByHost(new User.Builder().setUsername(username).build());
    }

    public Stay findByIdAndHost(Long stayId, String username) throws StayNotExistException {
        Stay stay = stayRepository.findByIdAndHost(stayId, new User.Builder().setUsername(username).build());
        if (stay == null) {
            throw new StayNotExistException("Stay doesn't exist");
        }
        return stay;
    }

    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void add(Stay stay, MultipartFile[] images) {
        List<String> mediaLinks = Arrays.stream(images).parallel().map(image -> imageStorageService.save(image)).collect(Collectors.toList());
        List<StayImage> stayImages = new ArrayList<>();
        for (String mediaLink : mediaLinks) {
            stayImages.add(new StayImage(mediaLink, stay));
        }
        stay.setImages(stayImages);

        stayRepository.save(stay);

        Location location = geoCodingService.getLatLng(stay.getId(), stay.getAddress());
        locationRepository.save(location);

    }

    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void delete(Long stayId, String username) throws StayNotExistException {
        Stay stay = stayRepository.findByIdAndHost(stayId, new User.Builder().setUsername(username).build());
        if (stay == null) {
            throw new StayNotExistException("Stay doesn't exist");
        }
        stayRepository.deleteById(stayId);
    }

}
  1. Go to CustomExceptionHandler class to include GeoCodingException and InvalidStayAddressException.
     @ControllerAdvice
public class CustomExceptionHandler {

    ...

    @ExceptionHandler(GeoCodingException.class)
    public final ResponseEntity<String> handleGeoCodingExceptions(Exception ex, WebRequest request) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(InvalidStayAddressException.class)
    public final ResponseEntity<String> handleInvalidStayAddressExceptions(Exception ex, WebRequest request) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}
  1. Update the SecrityConfig to include the search service.

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers(HttpMethod.POST, "/register/*").permitAll()
                    .antMatchers(HttpMethod.POST, "/authenticate/*").permitAll()
                    .antMatchers("/stays").hasAuthority("ROLE_HOST")
                    .antMatchers("/stays/*").hasAuthority("ROLE_HOST")
                    .antMatchers("/search").hasAuthority("ROLE_GUEST")
                    .anyRequest().authenticated()
                    .and()
                    .csrf()
                    .disable();
            http
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    
        }
    

Test Your Service

  1. Save all your changes and start your project. Make sure there’s no error in the log.

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2024-07-29T14:19:30.708-05:00 ERROR 41488 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :


APPLICATION FAILED TO START


Description:

Parameter 1 of method elasticsearchOperations in org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration required a bean of type 'org.elasticsearch.client.RestHighLevelClient' that could not be found.

Action:

Consider defining a bean of type 'org.elasticsearch.client.RestHighLevelClient' in your configuration.

Process finished with exit code 1

Screenshot 2024-07-29 at 21.42.48

  1. Open Postman and use the Host Authentication request to login as a host user.

Screenshot 2024-07-29 at 23.22.25

Screenshot 2024-07-29 at 21.43.39

  1. Select the Stay Upload request. Under the Authorization tab, copy the token generated in the last step.

Screenshot 2024-07-29 at 23.22.39

  1. Under the request body tab, select several images from your laptop. You can also change other parameters if you want.

Address should be real if not it can not find it will bug

  1. Send the request and make sure there are no errors in the response.

Screenshot 2024-07-29 at 23.21.19

  1. Use the Stay Geolocation Search request. Change the YOUR_GCE_INSTANCE_EXTERNAL_IP with your own IP address.

  2. Under the Authorization tab, put your Elasticsearch user and password.

  3. Send the request and make sure you can see one hit in the response.

    Screenshot 2024-07-29 at 23.26.57

    Screenshot 2024-07-29 at 23.30.26

  4. Open Postman and use the Guest Authentication request to log in as a guest user

Screenshot 2024-07-29 at 23.28.10

  1. Finally, use the Stay Search request and under the Authorization tab, add the token generated from the last step.

    Screenshot 2024-07-29 at 23.31.56

    When you paste token, you have make sure token is clean.