package com.spatial4j.demo.app; import com.google.common.base.Throwables; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.shape.Shape; import com.spatial4j.demo.KMLHelper; import com.spatial4j.demo.SampleDataLoader; import de.micromata.opengis.kml.v_2_2_0.Kml; import org.apache.commons.lang.time.DurationFormatUtils; import org.apache.lucene.spatial.prefix.tree.Cell; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrServer; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.SolrParams; import org.apache.wicket.AttributeModifier; import org.apache.wicket.PageParameters; import org.apache.wicket.RequestCycle; import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton; import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.repeater.RepeatingView; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget; import org.apache.wicket.util.file.File; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.resource.StringResourceStream; import org.apache.wicket.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.StringWriter; import java.net.URLEncoder; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SearchPage extends WebPage { static Logger log = LoggerFactory.getLogger( SearchPage.class ); static SampleDataLoader loader = new SampleDataLoader(); public static final ExecutorService pool = Executors.newCachedThreadPool(); // Dirty Dirty Dirty Hack... //static final SpatialPrefixTree grid = new QuadPrefixTree( -180, 180, -90-180, 90, 16 ); static final SpatialPrefixTree grid = new GeohashPrefixTree(JtsSpatialContext.GEO,GeohashPrefixTree.getMaxLevelsPossible()); static final SolrServer solr; static { solr = new HttpSolrServer( "http://localhost:8080/solr" ); } final IModel<Query> query = new Model<Query>( new Query() ); final IModel<QueryResponse> queryResponse; final IModel<String> error = new Model<String>( null ); final IModel<Long> elapsed = new Model<Long>( null ); final WebMarkupContainer results; public SearchPage(final PageParameters parameters) { add( new BookmarkablePageLink<Void>( "playground", PlaygroundPage.class ) ); Form<Query> searchForm = new Form<Query>( "search", new CompoundPropertyModel<Query>(query) ); searchForm.add( new DropDownChoice<String>("source", Arrays.asList( "(all)", "world-cities-points.txt", "countries-poly.txt", "countries-bbox.txt", "states-poly.txt", "states-bbox.txt" ) )); searchForm.add( new TextField<String>( "fq" ) ); searchForm.add( new DropDownChoice<String>("field", Arrays.asList( "geo", "ptvector", "bbox", "quad", "geohash" ) )); searchForm.add( new DropDownChoice<SpatialOperation>("op", SpatialOperation.values() )); searchForm.add( new TextField<String>( "geo" ) ); searchForm.add( new IndicatingAjaxButton( "submit" ) { @Override protected void onSubmit(AjaxRequestTarget target, Form<?> form) { target.addComponent( results ); } }); searchForm.add( new CheckBox( "score" ) ); searchForm.add( new TextField<String>( "distErrPct" ) ); searchForm.add( new TextField<String>( "sort" ) ); add( searchForm ); queryResponse = new LoadableDetachableModel<QueryResponse>() { @Override protected QueryResponse load() { long now = System.currentTimeMillis(); QueryResponse rsp = null; error.setObject( null ); try { rsp = solr.query( query.getObject().toSolrQuery( 100 ) ); } catch (SolrServerException ex) { Throwable t = ex.getCause(); if( t == null ) { t = ex; } log.warn( "unable to execute query", ex ); error.setObject( Throwables.getStackTraceAsString(t) ); } catch (Throwable ex) { log.warn( "unable to execute query", ex ); error.setObject( Throwables.getStackTraceAsString(ex) ); } elapsed.setObject( System.currentTimeMillis()-now ); return rsp; } }; results = new WebMarkupContainer( "results", queryResponse ) { @Override protected void onBeforeRender() { RepeatingView rv = new RepeatingView( "item" ); replace( rv ); QueryResponse rsp = queryResponse.getObject(); if( rsp != null ) { for( SolrDocument doc : rsp.getResults() ) { final String id = (String)doc.getFieldValue( "id" ); WebMarkupContainer row = new WebMarkupContainer( rv.newChildId() ); row.add( new Label( "name", doc.getFieldValue( "id" ) + " - " + doc.getFieldValue( "name" ) ) ); row.add( new Label( "source", (String)doc.getFieldValue( "source" ) )); row.add( new Label( "score", doc.getFieldValue( "score" )+"" )); row.add( new ExternalLink( "link", "/solr/select?q=id:"+ClientUtils.escapeQueryChars(id) )); row.add( addKmlLink( "kml", id, "shape" )); rv.add( row ); } } super.onBeforeRender(); } }; results.setOutputMarkupId( true ); results.add( new WebMarkupContainer( "item" ) ); // will get replaced results.add( new Label("count", new AbstractReadOnlyModel<String>() { @Override public String getObject() { QueryResponse rsp = queryResponse.getObject(); String v = null; if( rsp == null ) { v = "unable to execute query"; } else { SolrDocumentList docs = rsp.getResults(); v = docs.getStart() + " - " + (docs.getStart()+docs.size()) + " of " + docs.getNumFound(); } v += " ["+DurationFormatUtils.formatDurationHMS(elapsed.getObject())+"]"; return v; } })); results.add( new Label( "solr", new AbstractReadOnlyModel<String>() { @Override public String getObject() { SolrParams params = query.getObject().toSolrQuery( 10 ); return params.get( "q" ); } }).add( new AttributeModifier( "href", true, new AbstractReadOnlyModel<CharSequence>() { @Override public CharSequence getObject() { StringBuilder url = new StringBuilder(); url.append( "http://localhost:8080/solr/select?debugQuery=true" ); SolrParams params = query.getObject().toSolrQuery( 10 ); for(Iterator<String> it=params.getParameterNamesIterator(); it.hasNext(); ) { final String name = it.next(); try { final String [] values = params.getParams(name); if( values.length > 0 ) { for( String v : values ) { url.append( "&"+URLEncoder.encode( name, "UTF-8" ) ); url.append( '=' ); url.append( URLEncoder.encode( v, "UTF-8" ) ); } } else { url.append( "&"+URLEncoder.encode( name, "UTF-8" ) ); } } catch( Exception ex ) { ex.printStackTrace(); } } return url; } }))); results.add( new Label( "error", error ) { @Override public boolean isVisible() { return error.getObject() != null; } }); add( results ); final WebMarkupContainer load = new WebMarkupContainer( "load" ); load.setOutputMarkupId( true ); load.add( new IndicatingAjaxLink<Void>( "link" ) { @Override public void onClick(AjaxRequestTarget target) { pool.execute( new Runnable() { @Override public void run() { try { File data = WicketApplication.getDataDir(); //20 queue size, 3 concurrent threads SolrServer sss = new ConcurrentUpdateSolrServer( "http://localhost:8080/solr", 20, 3 ); // single thread if(false) { sss = solr; // new HttpSolrServer("http://localhost:8080/solr"); } loader.loadSampleData( data, sss ); } catch (Exception e) { e.printStackTrace(); } } }); load.add( new AbstractAjaxTimerBehavior( Duration.seconds(1) ) { @Override protected void onTimer(AjaxRequestTarget target) { System.out.println( "running: "+loader.status ); if( !loader.running ) { this.stop(); setResponsePage( getPage() ); } target.addComponent( load ); } }); try { Thread.sleep( 100 ); } catch (InterruptedException e) { return;//we were asked to stop } target.addComponent( load ); } @Override public boolean isVisible() { return !loader.running; } }); WebMarkupContainer status = new WebMarkupContainer( "status" ) { @Override public boolean isVisible() { return loader.running; } }; load.add( status ); status.add( new Label( "history", new AbstractReadOnlyModel<CharSequence>() { @Override public CharSequence getObject() { StringBuilder str = new StringBuilder(); for( String line : loader.history ) { str.append( line ).append( "\n" ); } return str; } })); status.add( new Label( "status", new AbstractReadOnlyModel<CharSequence>() { @Override public CharSequence getObject() { return "Loading: "+loader.name+" ("+loader.count + ") " + loader.status; } })); add( load ); } public Link<Void> addKmlLink( String id, final String docID, final String field ) { return new Link<Void>( "kml" ) { @Override public void onClick() { StringWriter out = new StringWriter(); Kml kml = getKML( docID, field ); kml.marshal( out ); final String name = kml.getFeature().getName(); IResourceStream resourceStream = new StringResourceStream( out.getBuffer(), "application/vnd.google-earth.kml+xml" ); getRequestCycle().setRequestTarget(new ResourceStreamRequestTarget(resourceStream) { @Override public String getFileName() { return name+".kml"; } @Override public void respond(RequestCycle requestCycle) { super.respond(requestCycle); } }); } }; } public Kml getKML( String id, String field ) { try { QueryResponse rsp = solr.query( new SolrQuery( "id:"+id ).setFields( "name,"+field ) ); SolrDocumentList docs = rsp.getResults(); if( docs.size() > 0 ) { String name = (String)docs.get(0).get( "name" ); // for multi valued fields, just use the first... String shapeString = (String)docs.get(0).getFirstValue( field ); Shape shape = grid.getSpatialContext().readShape(shapeString); SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape); double distErr = args.resolveDistErr(grid.getSpatialContext(), SpatialArgs.DEFAULT_DISTERRPCT); int detailLevel = grid.getLevelForDistance(distErr); List<Cell> cells = grid.getCells(shape, detailLevel, false, true);//false = no intermediates List<String> tokens = SpatialPrefixTree.cellsToTokenStrings(cells); return KMLHelper.toKML(name, grid, tokens); } } catch( Exception ex ) { ex.printStackTrace(); } return null; } }