content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.camel.component.geocoder;
  
  import java.util.Locale;
  
  import com.google.maps.GeoApiContext;
  import com.google.maps.GeocodingApi;
  import com.google.maps.GeolocationApi;
  import com.google.maps.errors.InvalidRequestException;
  import com.google.maps.errors.OverDailyLimitException;
  import com.google.maps.errors.OverQueryLimitException;
  import com.google.maps.errors.RequestDeniedException;
  import com.google.maps.errors.UnknownErrorException;
  import com.google.maps.model.AddressComponent;
  import com.google.maps.model.AddressType;
  import com.google.maps.model.GeocodingResult;
  import com.google.maps.model.GeolocationPayload;
  import com.google.maps.model.GeolocationResult;
  import com.google.maps.model.LatLng;
  import org.apache.camel.Exchange;
  import org.apache.camel.support.DefaultProducer;
  import org.apache.camel.util.StringHelper;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * The GeoCoder producer for Google.
   */
  public class GeoCoderGoogleProducer extends DefaultProducer {
      private static final Logger LOG = LoggerFactory.getLogger(GeoCoderGoogleProducer.class);
  
      private GeoCoderEndpoint endpoint;
      private GeoApiContext context;
  
      public GeoCoderGoogleProducer(GeoCoderEndpoint endpoint) {
          super(endpoint);
          this.endpoint = endpoint;
      }
  
      @Override
      public void process(Exchange exchange) throws Exception {
          try {
              // headers take precedence
              String address = exchange.getIn().getHeader(GeoCoderConstants.ADDRESS, String.class);
              if (address == null) {
                  address = endpoint.getAddress();
              }
  
              String latlng = exchange.getIn().getHeader(GeoCoderConstants.LATLNG, String.class);
              if (latlng == null) {
                  latlng = endpoint.getLatlng();
              }
  
              if (latlng != null) {
                  String lat = StringHelper.before(latlng, ",");
                  String lng = StringHelper.after(latlng, ",");
                  LatLng latLng = new LatLng(Double.parseDouble(lat), Double.parseDouble(lng));
  
                  LOG.debug("Geocode for lat/lng {}", latlng);
                  GeocodingResult[] results = GeocodingApi.reverseGeocode(context, latLng).await();
  
                  LOG.debug("Geocode response {}", results);
  
                  if (results != null) {
                      extractGeoResult(results, exchange);
                  }
  
              } else if (address != null) {
  
                  // is it current address
                  if ("current".equals(address)) {
                      processCurrentLocation(exchange);
                  } else {
  
                      LOG.debug("Geocode for address {}", address);
                      GeocodingResult[] results = GeocodingApi.geocode(context, address).await();
                      LOG.debug("Geocode response {}", results);
  
                      if (results != null) {
                          extractGeoResult(results, exchange);
                      }
                  }
              }
          } catch (RequestDeniedException e) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.REQUEST_DENIED);
          } catch (OverQueryLimitException e) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.OVER_QUERY_LIMIT);
          } catch (OverDailyLimitException e) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.OVER_DAILY_LIMIT);
          } catch (InvalidRequestException e) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.INVALID_REQUEST);
          } catch (UnknownErrorException e) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.UNKNOWN_ERROR);
          }
      }
  
      /**
       * Perform geolocation to retrieve LatLng and then perform a geocoding
       */
      protected void processCurrentLocation(Exchange exchange) throws Exception {
          LOG.debug("Geolocation for current location");
          GeolocationPayload payload = new GeolocationPayload();
          payload.considerIp = true;
          GeolocationResult result = GeolocationApi.geolocate(context, payload).await();
  
          LOG.debug("Geolocation response {}", result);
          // status
          exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.OK);
  
          // latlng
          String resLatlng = result.location.toString();
          exchange.getIn().setHeader(GeoCoderConstants.LATLNG, resLatlng);
  
          // address - reverse geocode
          LOG.debug("Geocode - reverse geocode for location {}", resLatlng);
          GeocodingResult[] results = GeocodingApi.reverseGeocode(context, result.location).await();
  
          LOG.debug("Geocode response {}", results);
  
          if (results != null) {
              extractGeoResult(results, exchange);
          }
      }
  
      private void setLatLngToExchangeHeader(LatLng location, Exchange exchange) {
          double resLat = location.lat;
          double resLng = location.lng;
          exchange.getIn().setHeader(GeoCoderConstants.LAT, formatLatOrLon(resLat));
          exchange.getIn().setHeader(GeoCoderConstants.LNG, formatLatOrLon(resLng));
          String resLatlng = location.toString();
          exchange.getIn().setHeader(GeoCoderConstants.LATLNG, resLatlng);
      }
  
      protected void extractGeoResult(GeocodingResult[] res, Exchange exchange) {
          // should we include body
          if (!endpoint.isHeadersOnly()) {
              exchange.getIn().setBody(res);
          }
          // no results
          if (res.length == 0) {
              exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.ZERO_RESULTS);
              return;
          }
  
          exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.OK);
  
          GeocodingResult first = res[0];
          exchange.getIn().setHeader(GeoCoderConstants.ADDRESS, first.formattedAddress);
          // just grab the first element and its lat and lon
          setLatLngToExchangeHeader(first.geometry.location, exchange);
  
          // additional details
          AddressComponent country = getCountry(res);
          if (country != null) {
              exchange.getIn().setHeader(GeoCoderConstants.COUNTRY_SHORT, country.shortName);
              exchange.getIn().setHeader(GeoCoderConstants.COUNTRY_LONG, country.longName);
          }
  
          AddressComponent city = getCity(res);
          if (city != null) {
              exchange.getIn().setHeader(GeoCoderConstants.CITY, city.longName);
          }
  
          AddressComponent postalCode = getPostalCode(res);
          if (postalCode != null) {
              exchange.getIn().setHeader(GeoCoderConstants.POSTAL_CODE, postalCode.shortName);
          }
  
          AddressComponent region = getRegion(res);
          if (region != null) {
              exchange.getIn().setHeader(GeoCoderConstants.REGION_CODE, region.shortName);
              exchange.getIn().setHeader(GeoCoderConstants.REGION_NAME, region.longName);
          }
      }
  
      private String formatLatOrLon(double value) {
          return String.format(Locale.ENGLISH, "%.8f", value);
      }
  
      private static AddressComponent getComponent(GeocodingResult[] results, AddressType addressType) {
          for (GeocodingResult result : results) {
              for (AddressType type : result.types) {
                  if (type == addressType && result.addressComponents.length > 0) {
                      return result.addressComponents[0];
                  }
              }
          }
          return null;
      }
  
      private static AddressComponent getCountry(GeocodingResult[] res) {
          return getComponent(res, AddressType.COUNTRY);
      }
  
      private static AddressComponent getCity(GeocodingResult[] res) {
          return getComponent(res, AddressType.LOCALITY);
      }
  
      private static AddressComponent getPostalCode(GeocodingResult[] res) {
          return getComponent(res, AddressType.POSTAL_CODE);
      }
  
      private static AddressComponent getRegion(GeocodingResult[] res) {
          return getComponent(res, AddressType.ADMINISTRATIVE_AREA_LEVEL_1);
      }
  
      @Override
      protected void doStart() {
          context = endpoint.createGeoApiContext();
      }
  }
  