/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. 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 com.esri.gpt.catalog.lucene;
import com.esri.gpt.framework.collection.StringAttribute;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.context.ApplicationConfiguration;
import com.esri.gpt.framework.context.ApplicationContext;
import com.esri.gpt.framework.http.HttpClientRequest;
import com.esri.gpt.framework.util.Val;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Locator.
*/
public class Locator {
/** geolocator service URL **/
protected String url;
/**
* Creates new instance of the locator.
* @return locator
* @throws IllegalArgumentException if "lucene.locatorClass" parameter is invalid
*/
public static Locator newInstance() {
ApplicationContext appCtx = ApplicationContext.getInstance();
ApplicationConfiguration appCfg = appCtx.getConfiguration();
String locatorClassName = appCfg.getCatalogConfiguration().getParameters().getValue("lucene.locatorClass");
if (Val.chkStr(locatorClassName).length() == 0) {
return new Locator();
} else {
try {
Class locatorClass = Class.forName(locatorClassName);
return (Locator) locatorClass.newInstance();
} catch (Exception ex) {
throw new IllegalArgumentException("Invalid lucene.locatorClass parameter: " + locatorClassName);
}
}
}
/**
* Finds candidates.
* @param text address text
* @return array of candidates
* @throws IOException if accessing geolocator service fails
* @throws ParseException if parsing address text fails
*/
public Candidate[] find(String text) throws IOException, ParseException {
String address = null, city = null, state = null, zip = null;
LinkedList<String> elements = new LinkedList<String>();
elements.addAll(Arrays.asList(Val.chkStr(text).split(",")));
if (elements.size() >= 3) {
zip = elements.removeLast();
state = elements.removeLast();
city = elements.removeLast();
} else if (elements.size() == 2) {
state = elements.removeLast();
city = elements.removeLast();
} else {
city = elements.removeLast();
}
// all the rest is just address
address = elements.toString();
return find(address, city, state, zip);
}
/**
* Finds best candidate from the array of candidates. Uses {@link Locator.ScoreComparator}.
* @param candidates array of candidates
* @return best candidate or <code>null</code> if no candidates can be found
*/
public Candidate findBestCandidate(Candidate[] candidates) {
return findBestCandidate(candidates, new ScoreComparator());
}
/**
* Finds best candidate from the array of candidates.
* @param candidates array of candidates
* @param comparator comparator
* @return best candidate or <code>null</code> if no candidates can be found
*/
public Candidate findBestCandidate(Candidate[] candidates, Comparator<Candidate> comparator) {
Candidate bestCandidate = null;
if (candidates != null) {
for (Locator.Candidate candidate : candidates) {
if (comparator.compare(bestCandidate, candidate) > 0) {
bestCandidate = candidate;
}
}
}
return bestCandidate;
}
/**
* Creates instance of the locator.
*/
protected Locator() {
ApplicationContext appCtx = ApplicationContext.getInstance();
ApplicationConfiguration appCfg = appCtx.getConfiguration();
this.url = appCfg.getInteractiveMap().getLocatorUrl();
}
/**
* Finds all candidates for the address.
* @param address address
* @param city city
* @param state state
* @param zip zip
* @return array of candidates
* @throws IOException if accessing geolocator service fails
*/
protected Candidate[] find(String address, String city, String state, String zip) throws IOException {
ArrayList<Candidate> candidates = new ArrayList<Candidate>();
address = Val.chkStr(address);
city = Val.chkStr(city);
state = Val.chkStr(state);
zip = Val.chkStr(zip);
String requestUrl = this.makeQueryUrl(address, city, state, zip);
HttpClientRequest client = HttpClientRequest.newRequest();
client.setUrl(requestUrl);
String sResponse = Val.chkStr(client.readResponseAsCharacters());
if (sResponse.length() > 0) {
try {
JSONObject jso = new JSONObject(sResponse);
JSONArray candidatesArr = jso.getJSONArray("candidates");
for (int i = 0; i < candidatesArr.length(); i++) {
JSONObject candidateObj = candidatesArr.getJSONObject(i);
String candidateAddress = candidateObj.getString("address");
JSONObject candidateLocation = candidateObj.getJSONObject("location");
long candidateScore = candidateObj.getLong("score");
JSONObject candidateAttributes = candidateObj.getJSONObject("attributes");
double candidateX = candidateLocation.getDouble("x");
double candidateY = candidateLocation.getDouble("y");
StringAttributeMap attributes = new StringAttributeMap();
JSONArray names = candidateAttributes.names();
for (int n = 0; n < (names != null ? names.length() : 0); n++) {
String name = names.getString(n);
String value = candidateAttributes.getString(name);
attributes.add(new StringAttribute(name, value));
}
Candidate candidate = new Candidate();
candidate.setAddress(candidateAddress);
candidate.setLocation(new double[]{candidateX, candidateY});
candidate.setScore(candidateScore);
candidate.setAttributes(attributes);
candidates.add(candidate);
}
} catch (JSONException ex) {
throw new IOException("Error reading response: "+ex.getMessage());
}
}
return candidates.toArray(new Candidate[candidates.size()]);
}
/**
* Makes query URL.
* @param address address
* @param city city
* @param state state
* @param zip zip
* @return query url
*/
protected String makeQueryUrl(String address, String city, String state, String zip) {
StringBuilder query = new StringBuilder();
if (address.length()>0) {
if (query.length() > 0) {
query.append("&");
}
query.append("Address=" + encode(address));
}
if (city.length()>0) {
if (query.length() > 0) {
query.append("&");
}
query.append("City=" + encode(city));
}
if (state.length()>0) {
if (query.length() > 0) {
query.append("&");
}
query.append("State=" + encode(state));
}
if (zip.length()>0) {
if (query.length() > 0) {
query.append("&");
}
query.append("Zip=" + encode(zip));
}
if (query.length() > 0) {
query.append("&");
}
query.append("f=pjson");
return url + "/findAddressCandidates?" + query;
}
/**
* Encodes string.
* @param s string to encode
* @return encoded string
*/
private String encode(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException ex) {
return s;
}
}
/**
* Score comparator.
*/
public static class ScoreComparator implements Comparator<Candidate> {
public int compare(Candidate o1, Candidate o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
if (o1.getScore() == o2.getScore()) {
return 0;
}
return o1.getScore() < o2.getScore() ? 1 : -1;
}
};
/**
* Candidate.
*/
public static class Candidate {
private String address;
private double[] location;
private long score;
private StringAttributeMap attributes;
/**
* Gets address.
* @return the address
*/
public String getAddress() {
return address;
}
/**
* Sets address.
* @param address the address to set
*/
void setAddress(String address) {
this.address = address;
}
/**
* Gets point.
* @return the point
*/
public double[] getLocation() {
return location;
}
/**
* Sets point
* @param point the point to set
*/
void setLocation(double[] point) {
this.location = point;
}
/**
* Gets score.
* @return the score
*/
public long getScore() {
return score;
}
/**
* Sets score.
* @param score the score to set
*/
void setScore(long score) {
this.score = score;
}
/**
* Gets attributes.
* @return the attributes
*/
public StringAttributeMap getAttributes() {
return attributes;
}
/**
* Sets attributes.
* @param attributes the attributes to set
*/
void setAttributes(StringAttributeMap attributes) {
this.attributes = attributes;
}
}
}