package com.spatial4j.demo.servlet;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.demo.KMLHelper;
import com.spatial4j.demo.app.WicketApplication;
import com.spatial4j.demo.io.SampleData;
import com.spatial4j.demo.io.SampleDataReader;
import de.micromata.opengis.kml.v_2_2_0.Kml;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GridInfoServlet extends HttpServlet
{
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public static int getIntParam( HttpServletRequest req, String p, int defaultValue )
{
String v = req.getParameter( p );
if( v != null && v.length() > 0 ) {
return Integer.parseInt( v );
}
return defaultValue;
}
public static double getDoubleParam( HttpServletRequest req, String p, double defaultValue )
{
String v = req.getParameter( p );
if( v != null && v.length() > 0 ) {
return Double.parseDouble( v );
}
return defaultValue;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
//Initialize the SpatialContext from the request parameters.
JtsSpatialContext ctx;
HashMap<String, String> ctxParams = new HashMap<String, String>();
ctxParams.put("spatialContextFactory", JtsSpatialContextFactory.class.getName());
ctxParams.put("geo", "true");
ctxParams.put("autoIndex", "true");
ctxParams.put("normWrapLongitude", "true");//our country dataset needs this
for (Map.Entry<String, String[]> entry : req.getParameterMap().entrySet()) {
ctxParams.put(entry.getKey(), (entry.getValue())[0]);
}
ctx = (JtsSpatialContext) SpatialContextFactory.makeSpatialContext(ctxParams, getClass().getClassLoader());
//
String name = req.getParameter( "name" );
Shape shape = null;
String country = req.getParameter( "country" );
if( country != null && country.length() == 3 ) {
InputStream in = WicketApplication.getStreamFromDataResource("countries-poly.txt");
try {
SampleDataReader reader = new SampleDataReader( in );
while( reader.hasNext() ) {
SampleData data = reader.next();
if( country.equalsIgnoreCase( data.id ) ) {
if (StringUtils.isEmpty(name))
name = data.name;
shape = ctx.readShapeFromWkt(data.shape);
break;
}
}
} catch (ParseException e) {
log(e.toString(), e);
res.sendError(HttpServletResponse.SC_BAD_REQUEST, e.toString());
return;
} finally {
IOUtils.closeQuietly(in);
}
if( shape == null ) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "unable to find: "+country );
return;
}
}
int depth = getIntParam( req, "depth", 16 );
String gridtype = req.getParameter("gridType");
SpatialPrefixTree grid;
if ("geohash".equals(gridtype)) {
grid = new GeohashPrefixTree(ctx, depth);
} else if ("quad".equals(gridtype)) {
grid = new QuadPrefixTree( ctx, depth );
} else {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "unknown grid type: "+gridtype );
return;
}
// If they don't set a country, then use the input
if( shape == null ) {
String geo = req.getParameter( "geometry" );
if( geo == null ) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing parameter: 'geometry'" );
return;
}
try {
shape = ctx.readShapeFromWkt(geo);
}
catch( Exception ex ) {
ex.printStackTrace();
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "error parsing geo: "+ex );
return;
}
}
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);
double distErrPct = getDoubleParam(req, "distErrPct", SpatialArgs.DEFAULT_DISTERRPCT);
double distErr = args.resolveDistErr(grid.getSpatialContext(), distErrPct);
int detailLevel = grid.getLevelForDistance(distErr);
List<Cell> allCells = grid.getCells(shape, detailLevel, true, true);
int totalCells = allCells.size();
List<Cell> leafCells = new ArrayList<>(allCells.size()/2);
int biggestLevel = 100;//that is a leaf
for (Cell cell : allCells) {
if (!cell.isLeaf())
continue;
leafCells.add(cell);
biggestLevel = Math.min(biggestLevel, cell.getLevel());
}
String msg = "Using detail level " + detailLevel + " (biggest is " + biggestLevel + ")" +
" yielding " + leafCells.size() + " leaf tokens, " + totalCells + " total.";
log(msg);
List<String> info = cellsToTokenStrings(leafCells);
String format = req.getParameter( "format" );
if( "kml".equals( format ) ) {
if( name == null || name.length() < 2 ) {
name = "KML - "+new Date( System.currentTimeMillis() );
}
Kml kml = KMLHelper.toKML( name, grid, info );
res.setHeader("Content-Disposition","attachment; filename=\"" + name + ".kml\";");
res.setContentType( "application/vnd.google-earth.kml+xml" );
kml.marshal( res.getOutputStream() );
return;
}
res.setContentType( "text/plain" );
PrintStream out = new PrintStream( res.getOutputStream() );
out.println(msg);
out.println( info.toString() );
}
/**
* Will add the trailing leaf byte for leaves. This isn't particularly efficient.
*/
private static List<String> cellsToTokenStrings(Collection<Cell> cells) {
List<String> tokens = new ArrayList<String>((cells.size()));
for (Cell cell : cells) {
final String token = cell.getTokenString();
if (cell.isLeaf()) {
tokens.add(token + '+');
} else {
tokens.add(token);
}
}
return tokens;
}
}