OriginIndexerModule.java

  1. package gov.usgs.earthquake.origin;

  2. import java.io.IOException;
  3. import java.math.BigDecimal;
  4. import java.util.Map;
  5. import java.util.logging.Level;
  6. import java.util.logging.Logger;

  7. import javax.json.JsonObject;

  8. import gov.usgs.earthquake.geoserve.GeoservePlacesService;
  9. import gov.usgs.earthquake.geoserve.GeoserveRegionsService;
  10. import gov.usgs.earthquake.indexer.DefaultIndexerModule;
  11. import gov.usgs.earthquake.indexer.IndexerModule;
  12. import gov.usgs.earthquake.indexer.ProductSummary;
  13. import gov.usgs.earthquake.product.Product;

  14. import gov.usgs.util.Config;
  15. import gov.usgs.util.StringUtils;

  16. /**
  17.  * Class for summarizing "origin" type products during the indexing process.
  18.  * Specifically this implementation uses a GeoservePlacesService to augment the
  19.  * properties on the product to include a "title" property if one is not already
  20.  * present.
  21.  *
  22.  * This module may be configured with the following properties: `endpointUrl`
  23.  * `connectTimeout`, and `readTimeout`.
  24.  */
  25. public class OriginIndexerModule extends DefaultIndexerModule {
  26.   private static final Logger LOGGER = Logger.getLogger(OriginIndexerModule.class.getName());

  27.   private GeoservePlacesService geoservePlaces;
  28.   private GeoserveRegionsService geoserveRegions;


  29.   /** Property for places endpoint url */
  30.   public static final String PLACES_ENDPOINT_URL_PROPERTY = "placesEndpointUrl";
  31.   /** property for regions endpoint url */
  32.   public static final String REGIONS_ENDPOINT_URL_PROPERTY = "regionsEndpointUrl";
  33.   /** property for connectTimeout */
  34.   public static final String CONNECT_TIMEOUT_PROPERTY = "connectTimeout";
  35.   /** Properties for readTimeout */
  36.   public static final String READ_TIMEOUT_PROPERTY = "readTimeout";

  37.   /** Property for Geoserve distance threshold */
  38.   public static final String GEOSERVE_DISTANCE_THRESHOLD_PROPERTY = "geoserveDistanceThreshold";

  39.   /**
  40.    * Distance threshold (in km), determines whether to use fe region
  41.    * or nearest place in the event title
  42.    */
  43.   public static final int DEFAULT_GEOSERVE_DISTANCE_THRESHOLD = 300;

  44.   private int distanceThreshold;

  45.   /**
  46.    * Empty constructor
  47.    * Do nothing, must be configured through bootstrapping before use
  48.    */
  49.   public OriginIndexerModule() {
  50.   }

  51.   /**
  52.    * Constructor
  53.    * @param geoservePlaces GeoservePlacesService
  54.    * @param geoserveRegions GeoserveRegionsService
  55.    */
  56.   public OriginIndexerModule(
  57.       final GeoservePlacesService geoservePlaces,
  58.       final GeoserveRegionsService geoserveRegions
  59.   ) {
  60.     this.setPlacesService(geoservePlaces);
  61.     this.setRegionsService(geoserveRegions);
  62.   }

  63.   /**
  64.    * @return The places service currently being used to return nearby places
  65.    */
  66.   public GeoservePlacesService getPlacesService() {
  67.     return this.geoservePlaces;
  68.   }

  69.   /**
  70.    * @return The regions service currently being used to return fe regions
  71.    */
  72.   public GeoserveRegionsService getRegionsService() {
  73.     return this.geoserveRegions;
  74.   }

  75.   /**
  76.    * @return The distance threshold currently being used to default to FE region
  77.    */
  78.   public int getDistanceThreshold() {
  79.     return this.distanceThreshold;
  80.   }

  81.   @Override
  82.   public ProductSummary getProductSummary(Product product) throws Exception {
  83.     ProductSummary summary = super.getProductSummary(product);
  84.     BigDecimal latitude = summary.getEventLatitude();
  85.     BigDecimal longitude = summary.getEventLongitude();

  86.     // Defer to existing title property if set...
  87.     Map<String, String> summaryProperties = summary.getProperties();
  88.     String title = summaryProperties.get("title");

  89.     if (title == null && latitude != null && longitude != null) {
  90.       try {
  91.         title = this.getEventTitle(latitude, longitude);
  92.         summaryProperties.put("title", StringUtils.encodeAsUtf8(title));
  93.       } catch (Exception ex) {
  94.         LOGGER
  95.             .warning(String.format("[%s] %s for product %s", this.getName(), ex.getMessage(), product.getId().toString()));
  96.         // Do nothing, value-added failed. Move on.
  97.       }
  98.     }

  99.     return summary;
  100.   }

  101.   @Override
  102.   public int getSupportLevel(Product product) {
  103.     int supportLevel = IndexerModule.LEVEL_UNSUPPORTED;
  104.     String type = getBaseProductType(product.getId().getType());

  105.     if ("origin".equals(type) && !"DELETE".equalsIgnoreCase(product.getStatus())) {
  106.       supportLevel = IndexerModule.LEVEL_SUPPORTED;
  107.     }

  108.     return supportLevel;
  109.   }

  110.   /**
  111.    * Set the GeoservePlacesService to be used for subsequent calls to GeoServe places
  112.    * endpoint.
  113.    *
  114.    * @param geoservePlaces The GeoservePlacesService to use
  115.    */
  116.   public void setPlacesService(GeoservePlacesService geoservePlaces) {
  117.     this.geoservePlaces = geoservePlaces;
  118.   }

  119.   /**
  120.    * Set the geoserveRegions to be used for subsequent calls to GeoServe regions
  121.    * endpoint.
  122.    *
  123.    * @param geoserveRegions The GeoserveRegions to use
  124.    */
  125.   public void setRegionsService(GeoserveRegionsService geoserveRegions) {
  126.     this.geoserveRegions = geoserveRegions;
  127.   }

  128.   /**
  129.    * Set the distance threshold to prefer fe region over nearst place
  130.    * in the event title
  131.    *
  132.    * @param threshold The distance threshold to use
  133.    */
  134.   public void setDistanceThreshold(int threshold) {
  135.     this.distanceThreshold = threshold;
  136.   }

  137.   @Override
  138.   public void configure(Config config) throws Exception {
  139.     // Distance threshold (in km)
  140.     this.distanceThreshold = Integer.parseInt(
  141.         config.getProperty(
  142.             GEOSERVE_DISTANCE_THRESHOLD_PROPERTY,
  143.             Integer.toString(DEFAULT_GEOSERVE_DISTANCE_THRESHOLD)
  144.         )
  145.     );

  146.     // Geoserve Places Endpoint configuration
  147.     String placesEndpointUrl = config.getProperty(
  148.         PLACES_ENDPOINT_URL_PROPERTY,
  149.         GeoservePlacesService.DEFAULT_ENDPOINT_URL
  150.     );
  151.     int placesEndpointConnectTimeout = Integer.parseInt(
  152.         config.getProperty(
  153.             CONNECT_TIMEOUT_PROPERTY,
  154.             Integer.toString(GeoservePlacesService.DEFAULT_CONNECT_TIMEOUT)
  155.         )
  156.     );
  157.     int placesEndpointReadTimeout = Integer.parseInt(
  158.         config.getProperty(
  159.             READ_TIMEOUT_PROPERTY,
  160.             Integer.toString(GeoservePlacesService.DEFAULT_READ_TIMEOUT)
  161.         )
  162.     );
  163.     LOGGER.config(
  164.         String.format("[%s] GeoservePlacesService(%s, %d, %d)",
  165.           this.getName(),
  166.           placesEndpointUrl,
  167.           placesEndpointConnectTimeout,
  168.           placesEndpointReadTimeout
  169.         )
  170.     );
  171.     this.setPlacesService(
  172.         new GeoservePlacesService(
  173.           placesEndpointUrl,
  174.           placesEndpointConnectTimeout,
  175.           placesEndpointReadTimeout
  176.         )
  177.     );

  178.     // Geoserve Regions Endpoint configuration
  179.     String regionsEndpointUrl = config.getProperty(
  180.         REGIONS_ENDPOINT_URL_PROPERTY,
  181.         GeoserveRegionsService.DEFAULT_ENDPOINT_URL
  182.     );
  183.     int regionsEndpointConnectTimeout = Integer.parseInt(
  184.         config.getProperty(
  185.             CONNECT_TIMEOUT_PROPERTY,
  186.             Integer.toString(GeoserveRegionsService.DEFAULT_CONNECT_TIMEOUT)
  187.         )
  188.     );
  189.     int regionsEndpointReadTimeout = Integer.parseInt(
  190.         config.getProperty(
  191.             READ_TIMEOUT_PROPERTY,
  192.             Integer.toString(GeoserveRegionsService.DEFAULT_READ_TIMEOUT)
  193.         )
  194.     );
  195.     LOGGER.config(
  196.         String.format("[%s] GeoserveRegionsService(%s, %d, %d)",
  197.             this.getName(),
  198.             regionsEndpointUrl,
  199.             regionsEndpointConnectTimeout,
  200.             regionsEndpointReadTimeout
  201.         )
  202.     );
  203.     this.setRegionsService(
  204.         new GeoserveRegionsService(
  205.             regionsEndpointUrl,
  206.             regionsEndpointConnectTimeout,
  207.             regionsEndpointReadTimeout
  208.         )
  209.     );
  210.   }

  211.   /**
  212.    * Get the event title based on the name and location of the nearest
  213.    * place, or if the nearest place is outside of the distance threshold
  214.    * return the fe region name
  215.    *
  216.    * @param latitude event latitude in degrees
  217.    * @param longitude event longitude in degrees
  218.    *
  219.    * @return {String} event name
  220.    *
  221.    * @throws IOException if IO error occurs
  222.    */
  223.   public String getEventTitle(BigDecimal latitude, BigDecimal longitude) throws Exception, IOException {
  224.     StringBuffer messages = new StringBuffer();
  225.     String message = null;

  226.     try {
  227.       final JsonObject feature = this.geoservePlaces.getNearestPlace(
  228.           latitude,
  229.           longitude,
  230.           this.distanceThreshold
  231.       );

  232.       if (feature != null) {
  233.         return this.formatEventTitle(feature);
  234.       } else {
  235.         message = "Places service returned no places within distance threshold";
  236.         messages.append(message + ". ");
  237.         LOGGER.log(Level.INFO, "[" + this.getName() + "] " + message);
  238.       }
  239.     } catch (Exception e) {
  240.       message = "Failed to get nearest place from geoserve places service";
  241.       messages.append(message + ". ");
  242.       messages.append(e.getMessage() + ". ");
  243.       LOGGER.log(Level.INFO, "[" + this.getName() + "] " + message);
  244.     }

  245.     try {
  246.       return this.geoserveRegions.getFeRegionName(latitude, longitude);
  247.     } catch (Exception e) {
  248.       message = "Failed to get FE region name";
  249.       messages.append(message + ". ");
  250.       messages.append(e.getMessage() + ". ");
  251.       LOGGER.log(Level.INFO, "[" + this.getName() + "] .");
  252.     }

  253.     // If we get this far, things failed spectacularly, report the error
  254.     Exception e = new Exception(messages.toString());
  255.     e.fillInStackTrace();
  256.     throw e;
  257.   }

  258.   /**
  259.    * Takes properties from feature and formats them into a string
  260.    * @param feature feature to format
  261.    * @return string with distance, direction, name, and admin
  262.    */
  263.   public String formatEventTitle(JsonObject feature) {
  264.     JsonObject properties = feature.getJsonObject("properties");

  265.     String name = properties.getString("name");
  266.     String country = properties.getString("country_code").toLowerCase();
  267.     String admin = properties.getString("country_name");
  268.     int distance = properties.getInt("distance");
  269.     double azimuth = properties.getJsonNumber("azimuth").doubleValue();
  270.     String direction = azimuthToDirection(azimuth);

  271.     if ("us".equals(country)) {
  272.       admin = properties.getString("admin1_name");
  273.     }

  274.     return String.format("%d km %s of %s, %s", distance, direction, name, admin);
  275.   }

  276.   /**
  277.    * Converts a decimal degree azimuth to a canonical compass direction
  278.    *
  279.    * @param azimuth The degrees azimuth to be converted
  280.    *
  281.    * @return {String} The canonical compass direction for the given input azimuth
  282.    */
  283.   public String azimuthToDirection(double azimuth) {
  284.     double fullwind = 22.5;
  285.     String[] directions = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW",
  286.         "NNW", "N" };

  287.     // Invert azimuth for proper directivity
  288.     // Maybe not needed in the future.
  289.     azimuth += 180.0;

  290.     // adjust azimuth if negative
  291.     while (azimuth < 0.0) {
  292.       azimuth = azimuth + 360.0;
  293.     }

  294.     return directions[(int) Math.round((azimuth % 360.0) / fullwind)];
  295.   }

  296.   public static void main(String[] args) throws Exception {
  297.     BigDecimal latitude = new BigDecimal("0.0");
  298.     BigDecimal longitude = new BigDecimal("0.0");
  299.     int maxradiuskm = DEFAULT_GEOSERVE_DISTANCE_THRESHOLD;
  300.     final OriginIndexerModule module = new OriginIndexerModule(
  301.       new GeoservePlacesService(),
  302.       new GeoserveRegionsService()
  303.     );
  304.     module.setName("TestModule");

  305.     for (String arg : args) {
  306.       if (arg.startsWith("--latitude=")) {
  307.         latitude = new BigDecimal(arg.replace("--latitude=", ""));
  308.       } else if (arg.startsWith("--longitude=")) {
  309.         longitude = new BigDecimal(arg.replace("--longitude=", ""));
  310.       } else if (arg.startsWith("--maxradiuskm=")) {
  311.         maxradiuskm = Integer.parseInt(arg.replace("--maxradiuskm=", ""));
  312.       }
  313.     }

  314.     module.setDistanceThreshold(maxradiuskm);

  315.     System.out.printf("Title[%s, %s] = `%s`\n",
  316.         latitude.doubleValue(),
  317.         longitude.doubleValue(),
  318.         module.getEventTitle(latitude, longitude));
  319.   }
  320. }