PDA

View Full Version : WMS Client - GetFeatureInfo and CQL


what_nick
05-30-2011, 06:40 AM
There are a few nice opensource WMS clients around some in Java (gt-wms, worldwind) and some in JavaScript(Openlayers, OpenWebGlobe). The basic functionality of pulling Raster tiles works fine in all. I am interested in extending the WorldWind WMS client to include the getFeatureInfo call (if the capabilities say that a layer is Queryable) and CQL filters (Geoserver only ?).


This is a request for comments for 2 points of extension I am proposing:

1) doPick implemention in a QueryAble subclass of WMSTiled, the pick detection fires off a getFeatureInfo call and on completion call a configurable download completion delegate which can just dump in console, prettify and pop-up in an annotation balloon, draw a graph on a numeric/unique value attribute (which is what I am doing).

2) extending the URLRetriever with CQL queries and the local retriever. Typical webserver strategy is to name files in the cache using URL hash, the worldwind strategy of naming files in the cache causes a few conflicts e.g. with CQL request appended to layernames and style, sometimes if 2 WMS servers are hosted on the same base URL/port this causes conflicts too. Some sort of cache based on the hash of the WMS endpoint would solve this. The CQL queries would need to be added as a folder level before/after styles - after is better.

Cheers,

whatnick.

what_nick
06-10-2011, 07:03 AM
A very hard wired implementation of GetFeatureInfo which does time series plotting is attached:


package org.trikend.noisdemo.layer;

import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVList;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.event.PositionEvent;
import gov.nasa.worldwind.event.PositionListene r;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.TextureTile;
import gov.nasa.worldwind.render.ScreenImage;
import gov.nasa.worldwind.retrieve.AbstractRetr ievalPostProcessor;
import gov.nasa.worldwind.retrieve.URLRetriever ;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWIO;
import gov.nasa.worldwind.util.WWXML;
import gov.nasa.worldwind.wms.WMSTiledImageLaye r;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.osgi.service.log.LogEntry;
import org.trikend.chart.api.IChart;
import org.w3c.dom.Document;

public class NOISWMSLayer extends WMSTiledImageLayer {

public static final String noisWMSBase = "http://stadler.hba.marine.csiro.au:8080/geoserver/wms";
public static final String noisWMSLayer = "ECOBASE:S15_1990_2009_GEOM";
public static final String noisWMSClosures = "ECOBASE:CLS_MERGED_TESTDATE";
private ScreenImage _chartSink;
private IChart _chartSource;
private List<Double> _weightList = new ArrayList<Double>();
private List<Date> _dateList = new ArrayList<Date>();
private List<Date> _closeList = new ArrayList<Date>();

public NOISWMSLayer() {
super(getConfigurationDocument(),null);
setPickEnabled(true);
}

private static Document getConfigurationDocument() {
//FIXME Does not load in OSGi context return null
Document config = WWXML.openDocumentFile(
"config/EarthNOIS/ECOBASE_S15_1990_2009_GEOM.xml", null);
if(config==null)
{
String noisConfigXML =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
+"<Layer layerType=\"TiledImageLayer\" version=\"1\">"
+" <DisplayName>NOIS WMS Layer</DisplayName>"
+" <DatasetName>ECOBASE:S15_1990_2009_GEOM</DatasetName>"
+" <DataCacheName>stadler.hba.marine.csiro.au_8080/_geoserver_wms/ECOBASE_S15_1990_2009_GEOM/GENLOG_GEOM"
+" </DataCacheName>"
+" <Service serviceName=\"OGC:WMS\" version=\"1.1.1\">"
+" <LayerNames>ECOBASE:S15_1990_2009_GEOM</LayerNames>"
+" <StyleNames>GENLOG_GEOM</StyleNames>"
+" <GetMapURL>http://stadler.hba.marine.csiro.au:8080/geoserver/wms?SERVICE=WMS&amp;"
+" </GetMapURL>"
+" <GetCapabilitiesURL>http://stadler.hba.marine.csiro.au:8080/geoserver/wms?SERVICE=WMS&amp;"
+" </GetCapabilitiesURL>"
+" </Service>"
+" <FormatSuffix>.dds</FormatSuffix>"
+" <NumLevels count=\"19\" numEmpty=\"0\" />"
+" <Sector>"
+" <SouthWest>"
+" <LatLon latitude=\"-47.488\" longitude=\"138.128\" units=\"degrees\" />"
+" </SouthWest>"
+" <NorthEast>"
+" <LatLon latitude=\"-33.147\" longitude=\"152.139\" units=\"degrees\" />"
+" </NorthEast>"
+" </Sector>"
+" <TileOrigin>"
+" <LatLon latitude=\"-90.0\" longitude=\"-180.0\" units=\"degrees\" />"
+" </TileOrigin>"
+" <TileSize>"
+" <Dimension height=\"512\" width=\"512\" />"
+" </TileSize>"
+" <LevelZeroTileDelta>"
+" <LatLon latitude=\"36.0\" longitude=\"36.0\" units=\"degrees\" />"
+" </LevelZeroTileDelta>"
+" <ImageFormat>image/png</ImageFormat>"
+" <UseTransparentTextures>true</UseTransparentTextures>"
+"</Layer>";
java.io.InputStream inputStream = WWIO.getInputStreamFromString(noisConfig XML);
return WWXML.openDocumentStream(inputStream);
}
return config;
}

public String toString() {
Object o = this.getValue(AVKey.DISPLAY_NAME);
return o != null ? (String) o : "NOIS WMS Layer";
}

/**
* Perform GetFeatureInfo as per WMS client spec
*
* @param location
* @return
* @throws Exception
*/
public void getFeatureInfo(Position location) throws Exception {
//New info coming clear the caching lists
_weightList.clear();
_dateList.clear();
_closeList.clear();

AVList params = new AVListImpl();
params.setValue(AVKey.GET_MAP_URL, noisWMSBase);
params.setValue(AVKey.LAYER_NAMES, noisWMSLayer);
params.setValue(AVKey.WMS_VERSION,"1.1.1");

//Get the URL for catches
NOISWMSLayer.URLBuilder builder = new NOISWMSLayer.URLBuilder(params);
URL finfoUrl = builder.getFinfoURL(location);
URLRetriever retriever = URLRetriever.createRetriever(finfoUrl, new FInfoPostProcessor());
retriever.setConnectTimeout(10000);
retriever.setReadTimeout(100000);
retriever.call();

//Get the URL for closures
params.setValue(AVKey.LAYER_NAMES, NOISWMSLayer.noisWMSClosures);
builder = new NOISWMSLayer.URLBuilder(params);
finfoUrl = builder.getFinfoURL(location);
retriever = URLRetriever.createRetriever(finfoUrl, new FInfoPostProcessor());
retriever.setConnectTimeout(10000);
retriever.setReadTimeout(100000);
retriever.call();
}

@Override
protected void retrieveLocalImage(TextureTile tile, String baseloc, int lvl)
throws Exception {
// TODO Tag with CQL based cache (hash CQL)
super.retrieveLocalImage(tile, baseloc, lvl);
}

@Override
protected void retrieveRemoteImage(TextureTile tile, String baseloc, int lvl)
throws Exception {
// TODO Tag with CQL based cache use custom URL Retriever including CQL
super.retrieveRemoteImage(tile, baseloc, lvl);
}

public class WMSFInfoListener implements PositionListener {

@Override
public void moved(PositionEvent event) {
// TODO respond to position clicked events
Position newPos = event.getPosition();
if (newPos != null && levels.getSector().contains(newPos)) {
try {
getFeatureInfo(newPos);
} catch (Exception e) {
// TODO What if we can't get the Info ?
Logging.logger().severe("Could not retrieve info:"+e.getMessage());
//e.printStackTrace();
}
}
}
}

public class FInfoPostProcessor extends AbstractRetrievalPostProcessor
{
private Pattern fishies_pattern = Pattern.compile(".*\nKG = (.+?)\n.*DATE_START = (.+?)\n.*DATE_END = (.+?)\n.*",Pattern.DOTALL);
private Pattern closures_pattern = Pattern.compile(".*DATE_START = (.+?)\n.*",Pattern.DOTALL);
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

@Override
protected File doGetOutputFile() {
// TODO Cache FInfo Queries as needed
return null;
}

@Override
protected ByteBuffer handleTextContent() throws IOException{
String contentType = this.getRetriever().getContentType().tri m().toLowerCase();

if (contentType.contains("xml"))
return this.handleXMLContent();

if (contentType.contains("html"))
return this.handleHTMLContent();

//FIXME How big a buffer is appropriate for GetFeatureInfo Queries ?
try {
parseTextInfo(WWIO.byteBufferToString(th is.getRetriever().getBuffer(), 8192, null));
} catch (ParseException e) {
// TODO Some text parsing failed oops
Logging.logger().severe("Info parsing failed:"+e.getMessage());
//e.printStackTrace();
}
return null;
}

/**
* Parse some useful objects from the lump of text we get
* from WMS
* @param textInfo
* @throws ParseException
*/
private void parseTextInfo(String textInfo) throws ParseException
{
textInfo = textInfo.trim();
if(textInfo.startsWith("no features were found")) return;


//Split with the ---- , so wish we had JSON
String[] entries = textInfo.split("--------------------------------------------");

if(textInfo.startsWith("Results for FeatureType 'S15_1990_2009_GEOM':")) parseCatch(entries);
if(textInfo.startsWith("Results for FeatureType 'CLS_MERGED_TESTDATE':")) parseClosures(entries);

//Some Regexp Voodoo thanks to gary for grabbing fields

//FIXME Wiring to chart sink should happen in the layer not in this post processor
_chartSource.plot(_dateList.toArray(), _weightList.toArray());
_chartSource.annotate(_closeList.toArray ());
_chartSink.setImageSource(_chartSource.g etPlot());

}


/**
* Parse all the Catch value entries
* @param entries
* @throws ParseException
*/
private void parseCatch(String[] entries) throws ParseException {
for (String string : entries) {
Matcher matcher = fishies_pattern.matcher(string);
boolean matchFound = matcher.matches();

if (matchFound) {
if (matcher.groupCount() != 3) {
System.out.println("Not a complete match");
}
else
{
//Capture groups start at 1 what a pit-fall
_weightList.add(new Double(matcher.group(1)));
_dateList.add(dateFormat.parse(matcher.g roup(2)));
}

}
}
}

/**
* Parse all the closure entries
* @param entries
* @throws ParseException
*/
private void parseClosures(String[] entries) throws ParseException {
for (String string : entries) {
Matcher matcher = closures_pattern.matcher(string);
boolean matchFound = matcher.matches();

if (matchFound) {
if (matcher.groupCount() != 1) {
System.out.println("Not a complete match");
}
else
{
//Capture groups start at 1 what a pit-fall
_closeList.add(dateFormat.parse(matcher. group(1)));
}
}
}
}

}

public static class URLBuilder extends WMSTiledImageLayer.URLBuilder {
private static final String MAX_VERSION = "1.3.0";

private final String layerNames;
private final String wmsVersion;
private final String crs;
private final String wmsGetMap;
public String URLTemplate;

public URLBuilder(AVList params) {
super(params);
this.layerNames = params.getStringValue(AVKey.LAYER_NAMES) ;
String version = params.getStringValue(AVKey.WMS_VERSION) ;
wmsGetMap = params.getStringValue(AVKey.GET_MAP_URL) ;

if (version == null || version.compareTo(MAX_VERSION) >= 0) {
this.wmsVersion = MAX_VERSION;
this.crs = "&crs=CRS:84";
} else {
this.wmsVersion = version;
this.crs = "&srs=EPSG:4326";
}
}

/* Hack together a get featureinfo request with the template
* below and the handy StringBuffer
*
* http://stadler.hba.marine.csiro
* .au:8080/geoserver/wms?REQUEST=GetFeatureInfo
* &EXCEPTIONS=application%2Fvnd
* .ogc.se_xml&BBOX=131.17%2C-49.467984%2C159.09
* %2C-27.710016&X=342&Y=189&INFO_FORMAT
* =text%2Fplain&QUERY_LAYERS=ECOBASE%3AS15_1990_2009_GEO M
* &FEATURE_COUNT=100&
* Srs=EPSG%3A4326&Layers=ECOBASE%3AS15_1990_2009_GEOM&Styles
* =&WIDTH=512&HEIGHT=399&format=image%2Fpng
*/
public URL getFinfoURL(Position pos) throws MalformedURLException {
// Buffer position
final double eps = 0.00001D;
final double lat = pos.getLatitude().degrees;
final double lon = pos.getLongitude().degrees;
final double lat_max = lat + eps;
final double lon_max = lon + eps;
final double lat_min = lat - eps;
final double lon_min = lon - eps;

StringBuffer sb = new StringBuffer(wmsGetMap);
sb.append("?service=wms");
sb.append("&request=GetFeatureInfo");
sb.append("&version=").append(this.wmsVersion);
sb.append(this.crs);
sb.append("&info_format=text/plain");
sb.append("&bbox=");
sb.append(lon_min);
sb.append(",");
sb.append(lat_min);
sb.append(",");
sb.append(lon_max);
sb.append(",");
sb.append(lat_max);
sb.append("&layers="+layerNames);
sb.append("&query_layers="+layerNames);
sb.append("&width=1");
sb.append("&height=1");
sb.append("&x=0");
sb.append("&y=0");
sb.append("&feature_count=50");
return new URL(sb.toString());
}
}

public void setChartLayer(ScreenImage chart) {
// TODO Should be IChart in the framework tightly coupled for now
this._chartSink = chart;
}

public void setChartProducer(IChart chartSource) {
// TODO Auto-generated method stub
this._chartSource = chartSource;
}
}


I hope you find it handy. The WMS is behind a firewall, so please replace with your own, or if you work for CSIRO send me a cookie.

Cheers,

whatnick.

rhorner
06-26-2012, 04:26 PM
Appreciate your insight and code examples in the forum.

Attempting to do a getfeatureinfo in wwj against a geoserver wms layer and having some issues with it finding the features expected. Can getfeatureinfo be used if all I have is a lat/lon and not an image with the pixel position (x,y)? Is the example you have given attempting to do this by
setting:
sb.append("&width=1");
sb.append("&height=1");
sb.append("&x=0");
sb.append("&y=0");
and using 0.00001D as an offset for your bbox?

Thanks for any help you can give.

rhorner
06-27-2012, 03:29 PM
Got it working. Just had to modify the bbox so that the picked point was the upper left not center of bbox. (Upper left being 0,0 for the x,y).