package me.osm.gazetteer.web.api;
import static me.osm.gazetteer.web.api.utils.RequestUtils.getSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import me.osm.gazetteer.web.ESNodeHolder;
import me.osm.gazetteer.web.GazetteerWeb;
import me.osm.gazetteer.web.api.meta.Endpoint;
import me.osm.gazetteer.web.api.meta.Parameter;
import me.osm.gazetteer.web.api.utils.RequestUtils;
import me.osm.gazetteer.web.imp.IndexHolder;
import me.osm.gazetteer.web.utils.OSMDocSinglton;
import me.osm.osmdoc.localization.L10n;
import me.osm.osmdoc.model.Feature;
import me.osm.osmdoc.model.Tag;
import me.osm.osmdoc.read.OSMDocFacade;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Order;
import org.json.JSONArray;
import org.json.JSONObject;
import org.restexpress.Request;
import org.restexpress.Response;
import org.restexpress.domain.metadata.UriMetadata;
public class StatisticAPI implements DocumentedApi {
/**
* Features id's of higher objects to filter results.
* Array members will be added using OR
* */
public static final String REFERENCES_HEADER = "filter";
private String apiDefaultHierarchy;
public JSONObject read(Request request, Response response) {
Set<String> classes = RequestUtils.getSet(request, SearchAPI.POI_CLASS_HEADER);
Set<String> refs = getSet(request, REFERENCES_HEADER);
Locale locale = null;
if(L10n.supported.contains(request.getHeader("lang"))) {
locale = Locale.forLanguageTag(request.getHeader("lang"));
}
apiDefaultHierarchy = GazetteerWeb.osmdocProperties().getApiDefaultHierarchy();
String hname = request.getHeader(SearchAPI.HIERARCHY_CODE_HEADER, apiDefaultHierarchy);
SearchAPI.addPOIGroups(request, classes, hname);
boolean doc4Found = RequestUtils.getBooleanHeader(request, "doc4found", true);
OSMDocFacade osmdoc = OSMDocSinglton.get().getFacade();
List<Feature> features = new ArrayList<>();
for(String clazz : classes) {
Feature feature = osmdoc.getFeature(clazz);
if(feature != null) {
features.add(feature);
}
}
if(features.isEmpty()) {
response.setResponseCode(404);
return null;
}
Client client = ESNodeHolder.getClient();
BoolQueryBuilder filters = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("type", "poipnt"))
.must(QueryBuilders.termsQuery("poi_class", classes));
if(refs != null && !refs.isEmpty()) {
filters.must(QueryBuilders.termsQuery("refs", refs));
}
SearchRequestBuilder searchQ = client.prepareSearch("gazetteer")
.setTypes(IndexHolder.LOCATION)
.setQuery(filters);
JSONObject tagOptions = osmdoc.collectCommonTagsWithTraitsJSON(osmdoc.getFeature(classes), locale);
Set<String> allTagKeys = getTagKeys(tagOptions);
allTagKeys.removeAll(GazetteerWeb.osmdocProperties().getIgnoreTagsGrouping());
for(String tagKey : allTagKeys) {
searchQ.addAggregation(AggregationBuilders.terms(tagKey)
.field("more_tags." + tagKey).minDocCount(10));
}
searchQ.addAggregation(AggregationBuilders.terms("name").field("name.exact")
.minDocCount(10).size(25).order(Order.count(false)));
searchQ.setSearchType(SearchType.COUNT);
SearchResponse esResponse = searchQ.execute().actionGet();
Aggregations aggregations = esResponse.getAggregations();
JSONObject result = new JSONObject();
result.put("poi_class", new JSONArray(classes));
result.put("total_count", esResponse.getHits().getTotalHits());
result.put("tag_options", tagOptions);
// Order tags by key
JSONObject statistic = new JSONObject();
result.put("tagValuesStatistic", statistic);
for(Aggregation agg : aggregations.asList()) {
if(agg instanceof Terms) {
Terms termsAgg = (Terms) agg;
JSONObject values = new JSONObject();
for(Bucket bucket : termsAgg.getBuckets()) {
values.put(bucket.getKey(), bucket.getDocCount());
}
if("name".equals(agg.getName())) {
result.put("names", values);
}
else if("type".equals(agg.getName())) {
result.put("types", values);
}
else if(values.length() > 0) {
statistic.put(agg.getName(), values);
}
}
}
if(doc4Found) {
Set<String> foundedKeys = statistic.keySet();
Set<String> notFound = new HashSet<>(allTagKeys);
notFound.removeAll(foundedKeys);
JSONObject groupedTags = tagOptions.getJSONObject("groupedTags");
JSONArray options = tagOptions.getJSONArray("commonTagOptions");
for(String notFoundKey : notFound) {
groupedTags.remove(notFoundKey);
}
TreeSet<Integer> remove = new TreeSet<>();
for(int i = 0; i < options.length(); i++) {
JSONObject filter = options.getJSONObject(i);
if(notFound.contains(filter.getString("key"))) {
remove.add(i);
}
else if(filter.getString("key").startsWith("trait_")) {
JSONArray group = filter.optJSONArray("options");
TreeSet<Integer> gropRemove = new TreeSet<>();
for(int j = 0; j < group.length(); j++) {
if(notFound.contains(group.getJSONObject(j).getString("valueKey"))) {
gropRemove.add(j);
}
}
for(Iterator<Integer> gri = gropRemove.descendingIterator(); gri.hasNext();) {
group.remove(gri.next());
}
if(group.length() == 0) {
remove.add(i);
}
}
}
for(Iterator<Integer> ri = remove.descendingIterator(); ri.hasNext();) {
options.remove(ri.next());
}
}
return result;
}
private Set<String> getTagKeys(JSONObject tagOptions) {
Set<String> result = new HashSet<>();
JSONArray tagOptionsJSON = tagOptions.optJSONArray("commonTagOptions");
for(int i = 0; i < tagOptionsJSON.length(); i++) {
JSONObject jsonObject = tagOptionsJSON.getJSONObject(i);
if(!jsonObject.getString("type").equals("GROUP_TRAIT")) {
result.add(jsonObject.getString("key"));
}
}
result.addAll(tagOptions.getJSONObject("groupedTags").keySet());
return result;
}
@Override
public Endpoint getMeta(UriMetadata uriMetadata) {
Endpoint meta = new Endpoint(uriMetadata.getPattern(), "Tag values statistics",
"Tag values statistics for parsed tags.");
meta.getPathParameters().add(new Parameter("poi-class",
"Poi class code."));
return meta;
}
}