OriginIndexerModule.java
package gov.usgs.earthquake.origin;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.json.JsonObject;
import gov.usgs.earthquake.geoserve.GeoservePlacesService;
import gov.usgs.earthquake.geoserve.GeoserveRegionsService;
import gov.usgs.earthquake.indexer.DefaultIndexerModule;
import gov.usgs.earthquake.indexer.IndexerModule;
import gov.usgs.earthquake.indexer.ProductSummary;
import gov.usgs.earthquake.product.Product;
import gov.usgs.util.Config;
import gov.usgs.util.StringUtils;
/**
* Class for summarizing "origin" type products during the indexing process.
* Specifically this implementation uses a GeoservePlacesService to augment the
* properties on the product to include a "title" property if one is not already
* present.
*
* This module may be configured with the following properties: `endpointUrl`
* `connectTimeout`, and `readTimeout`.
*/
public class OriginIndexerModule extends DefaultIndexerModule {
private static final Logger LOGGER = Logger.getLogger(OriginIndexerModule.class.getName());
private GeoservePlacesService geoservePlaces;
private GeoserveRegionsService geoserveRegions;
/** Property for places endpoint url */
public static final String PLACES_ENDPOINT_URL_PROPERTY = "placesEndpointUrl";
/** property for regions endpoint url */
public static final String REGIONS_ENDPOINT_URL_PROPERTY = "regionsEndpointUrl";
/** property for connectTimeout */
public static final String CONNECT_TIMEOUT_PROPERTY = "connectTimeout";
/** Properties for readTimeout */
public static final String READ_TIMEOUT_PROPERTY = "readTimeout";
/** Property for Geoserve distance threshold */
public static final String GEOSERVE_DISTANCE_THRESHOLD_PROPERTY = "geoserveDistanceThreshold";
/**
* Distance threshold (in km), determines whether to use fe region
* or nearest place in the event title
*/
public static final int DEFAULT_GEOSERVE_DISTANCE_THRESHOLD = 300;
private int distanceThreshold;
/**
* Empty constructor
* Do nothing, must be configured through bootstrapping before use
*/
public OriginIndexerModule() {
}
/**
* Constructor
* @param geoservePlaces GeoservePlacesService
* @param geoserveRegions GeoserveRegionsService
*/
public OriginIndexerModule(
final GeoservePlacesService geoservePlaces,
final GeoserveRegionsService geoserveRegions
) {
this.setPlacesService(geoservePlaces);
this.setRegionsService(geoserveRegions);
}
/**
* @return The places service currently being used to return nearby places
*/
public GeoservePlacesService getPlacesService() {
return this.geoservePlaces;
}
/**
* @return The regions service currently being used to return fe regions
*/
public GeoserveRegionsService getRegionsService() {
return this.geoserveRegions;
}
/**
* @return The distance threshold currently being used to default to FE region
*/
public int getDistanceThreshold() {
return this.distanceThreshold;
}
@Override
public ProductSummary getProductSummary(Product product) throws Exception {
ProductSummary summary = super.getProductSummary(product);
BigDecimal latitude = summary.getEventLatitude();
BigDecimal longitude = summary.getEventLongitude();
// Defer to existing title property if set...
Map<String, String> summaryProperties = summary.getProperties();
String title = summaryProperties.get("title");
if (title == null && latitude != null && longitude != null) {
try {
title = this.getEventTitle(latitude, longitude);
summaryProperties.put("title", StringUtils.encodeAsUtf8(title));
} catch (Exception ex) {
LOGGER
.warning(String.format("[%s] %s for product %s", this.getName(), ex.getMessage(), product.getId().toString()));
// Do nothing, value-added failed. Move on.
}
}
return summary;
}
@Override
public int getSupportLevel(Product product) {
int supportLevel = IndexerModule.LEVEL_UNSUPPORTED;
String type = getBaseProductType(product.getId().getType());
if ("origin".equals(type) && !"DELETE".equalsIgnoreCase(product.getStatus())) {
supportLevel = IndexerModule.LEVEL_SUPPORTED;
}
return supportLevel;
}
/**
* Set the GeoservePlacesService to be used for subsequent calls to GeoServe places
* endpoint.
*
* @param geoservePlaces The GeoservePlacesService to use
*/
public void setPlacesService(GeoservePlacesService geoservePlaces) {
this.geoservePlaces = geoservePlaces;
}
/**
* Set the geoserveRegions to be used for subsequent calls to GeoServe regions
* endpoint.
*
* @param geoserveRegions The GeoserveRegions to use
*/
public void setRegionsService(GeoserveRegionsService geoserveRegions) {
this.geoserveRegions = geoserveRegions;
}
/**
* Set the distance threshold to prefer fe region over nearst place
* in the event title
*
* @param threshold The distance threshold to use
*/
public void setDistanceThreshold(int threshold) {
this.distanceThreshold = threshold;
}
@Override
public void configure(Config config) throws Exception {
// Distance threshold (in km)
this.distanceThreshold = Integer.parseInt(
config.getProperty(
GEOSERVE_DISTANCE_THRESHOLD_PROPERTY,
Integer.toString(DEFAULT_GEOSERVE_DISTANCE_THRESHOLD)
)
);
// Geoserve Places Endpoint configuration
String placesEndpointUrl = config.getProperty(
PLACES_ENDPOINT_URL_PROPERTY,
GeoservePlacesService.DEFAULT_ENDPOINT_URL
);
int placesEndpointConnectTimeout = Integer.parseInt(
config.getProperty(
CONNECT_TIMEOUT_PROPERTY,
Integer.toString(GeoservePlacesService.DEFAULT_CONNECT_TIMEOUT)
)
);
int placesEndpointReadTimeout = Integer.parseInt(
config.getProperty(
READ_TIMEOUT_PROPERTY,
Integer.toString(GeoservePlacesService.DEFAULT_READ_TIMEOUT)
)
);
LOGGER.config(
String.format("[%s] GeoservePlacesService(%s, %d, %d)",
this.getName(),
placesEndpointUrl,
placesEndpointConnectTimeout,
placesEndpointReadTimeout
)
);
this.setPlacesService(
new GeoservePlacesService(
placesEndpointUrl,
placesEndpointConnectTimeout,
placesEndpointReadTimeout
)
);
// Geoserve Regions Endpoint configuration
String regionsEndpointUrl = config.getProperty(
REGIONS_ENDPOINT_URL_PROPERTY,
GeoserveRegionsService.DEFAULT_ENDPOINT_URL
);
int regionsEndpointConnectTimeout = Integer.parseInt(
config.getProperty(
CONNECT_TIMEOUT_PROPERTY,
Integer.toString(GeoserveRegionsService.DEFAULT_CONNECT_TIMEOUT)
)
);
int regionsEndpointReadTimeout = Integer.parseInt(
config.getProperty(
READ_TIMEOUT_PROPERTY,
Integer.toString(GeoserveRegionsService.DEFAULT_READ_TIMEOUT)
)
);
LOGGER.config(
String.format("[%s] GeoserveRegionsService(%s, %d, %d)",
this.getName(),
regionsEndpointUrl,
regionsEndpointConnectTimeout,
regionsEndpointReadTimeout
)
);
this.setRegionsService(
new GeoserveRegionsService(
regionsEndpointUrl,
regionsEndpointConnectTimeout,
regionsEndpointReadTimeout
)
);
}
/**
* Get the event title based on the name and location of the nearest
* place, or if the nearest place is outside of the distance threshold
* return the fe region name
*
* @param latitude event latitude in degrees
* @param longitude event longitude in degrees
*
* @return {String} event name
*
* @throws IOException if IO error occurs
*/
public String getEventTitle(BigDecimal latitude, BigDecimal longitude) throws Exception, IOException {
StringBuffer messages = new StringBuffer();
String message = null;
try {
final JsonObject feature = this.geoservePlaces.getNearestPlace(
latitude,
longitude,
this.distanceThreshold
);
if (feature != null) {
return this.formatEventTitle(feature);
} else {
message = "Places service returned no places within distance threshold";
messages.append(message + ". ");
LOGGER.log(Level.INFO, "[" + this.getName() + "] " + message);
}
} catch (Exception e) {
message = "Failed to get nearest place from geoserve places service";
messages.append(message + ". ");
messages.append(e.getMessage() + ". ");
LOGGER.log(Level.INFO, "[" + this.getName() + "] " + message);
}
try {
return this.geoserveRegions.getFeRegionName(latitude, longitude);
} catch (Exception e) {
message = "Failed to get FE region name";
messages.append(message + ". ");
messages.append(e.getMessage() + ". ");
LOGGER.log(Level.INFO, "[" + this.getName() + "] .");
}
// If we get this far, things failed spectacularly, report the error
Exception e = new Exception(messages.toString());
e.fillInStackTrace();
throw e;
}
/**
* Takes properties from feature and formats them into a string
* @param feature feature to format
* @return string with distance, direction, name, and admin
*/
public String formatEventTitle(JsonObject feature) {
JsonObject properties = feature.getJsonObject("properties");
String name = properties.getString("name");
String country = properties.getString("country_code").toLowerCase();
String admin = properties.getString("country_name");
int distance = properties.getInt("distance");
double azimuth = properties.getJsonNumber("azimuth").doubleValue();
String direction = azimuthToDirection(azimuth);
if ("us".equals(country)) {
admin = properties.getString("admin1_name");
}
return String.format("%d km %s of %s, %s", distance, direction, name, admin);
}
/**
* Converts a decimal degree azimuth to a canonical compass direction
*
* @param azimuth The degrees azimuth to be converted
*
* @return {String} The canonical compass direction for the given input azimuth
*/
public String azimuthToDirection(double azimuth) {
double fullwind = 22.5;
String[] directions = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW",
"NNW", "N" };
// Invert azimuth for proper directivity
// Maybe not needed in the future.
azimuth += 180.0;
// adjust azimuth if negative
while (azimuth < 0.0) {
azimuth = azimuth + 360.0;
}
return directions[(int) Math.round((azimuth % 360.0) / fullwind)];
}
public static void main(String[] args) throws Exception {
BigDecimal latitude = new BigDecimal("0.0");
BigDecimal longitude = new BigDecimal("0.0");
int maxradiuskm = DEFAULT_GEOSERVE_DISTANCE_THRESHOLD;
final OriginIndexerModule module = new OriginIndexerModule(
new GeoservePlacesService(),
new GeoserveRegionsService()
);
module.setName("TestModule");
for (String arg : args) {
if (arg.startsWith("--latitude=")) {
latitude = new BigDecimal(arg.replace("--latitude=", ""));
} else if (arg.startsWith("--longitude=")) {
longitude = new BigDecimal(arg.replace("--longitude=", ""));
} else if (arg.startsWith("--maxradiuskm=")) {
maxradiuskm = Integer.parseInt(arg.replace("--maxradiuskm=", ""));
}
}
module.setDistanceThreshold(maxradiuskm);
System.out.printf("Title[%s, %s] = `%s`\n",
latitude.doubleValue(),
longitude.doubleValue(),
module.getEventTitle(latitude, longitude));
}
}